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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98enum ConnectorOperator {
99    And,
100    Or,
101}
102
103impl ConnectorOperator {
104    fn keyword(self) -> &'static str {
105        match self {
106            Self::And => "AND",
107            Self::Or => "OR",
108        }
109    }
110}
111
112/// Identifier quote style (start/end characters)
113#[derive(Debug, Clone, Copy, PartialEq)]
114pub struct IdentifierQuoteStyle {
115    /// Start character for quoting identifiers (e.g., '"', '`', '[')
116    pub start: char,
117    /// End character for quoting identifiers (e.g., '"', '`', ']')
118    pub end: char,
119}
120
121impl Default for IdentifierQuoteStyle {
122    fn default() -> Self {
123        Self {
124            start: '"',
125            end: '"',
126        }
127    }
128}
129
130impl IdentifierQuoteStyle {
131    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
132    pub const DOUBLE_QUOTE: Self = Self {
133        start: '"',
134        end: '"',
135    };
136    /// Backtick style (MySQL, BigQuery, Spark, Hive)
137    pub const BACKTICK: Self = Self {
138        start: '`',
139        end: '`',
140    };
141    /// Square bracket style (TSQL, SQLite)
142    pub const BRACKET: Self = Self {
143        start: '[',
144        end: ']',
145    };
146}
147
148/// Configuration for the SQL [`Generator`].
149///
150/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
151/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
152/// syntax, feature support flags, and more.
153///
154/// Most users should start from `GeneratorConfig::default()` and override only the
155/// fields they need. Dialect-specific presets are applied automatically when
156/// `dialect` is set via the higher-level transpilation API.
157///
158/// # Key fields
159///
160/// | Field | Default | Purpose |
161/// |-------|---------|---------|
162/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
163/// | `pretty` | `false` | Enable multi-line, indented output |
164/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
165/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
166/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
167/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
168/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
169#[derive(Debug, Clone)]
170pub struct GeneratorConfig {
171    // ===== Basic formatting =====
172    /// Pretty print with indentation
173    pub pretty: bool,
174    /// Indentation string (default 2 spaces)
175    pub indent: String,
176    /// Maximum text width before wrapping (default 80)
177    pub max_text_width: usize,
178    /// Quote identifier style (deprecated, use identifier_quote_style instead)
179    pub identifier_quote: char,
180    /// Identifier quote style with separate start/end characters
181    pub identifier_quote_style: IdentifierQuoteStyle,
182    /// Uppercase keywords
183    pub uppercase_keywords: bool,
184    /// Normalize identifiers to lowercase when generating
185    pub normalize_identifiers: bool,
186    /// Dialect type for dialect-specific generation
187    pub dialect: Option<crate::dialects::DialectType>,
188    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
189    pub source_dialect: Option<crate::dialects::DialectType>,
190    /// How to output function names (UPPER, lower, or as-is)
191    pub normalize_functions: NormalizeFunctions,
192    /// String escape character
193    pub string_escape: char,
194    /// Whether identifiers are case-sensitive
195    pub case_sensitive_identifiers: bool,
196    /// Whether unquoted identifiers can start with a digit
197    pub identifiers_can_start_with_digit: bool,
198    /// Whether to always quote identifiers regardless of reserved keyword status
199    /// Used by dialects like Athena/Presto that prefer quoted identifiers
200    pub always_quote_identifiers: bool,
201    /// How to render negated IN predicates in generic output.
202    pub not_in_style: NotInStyle,
203
204    // ===== Null handling =====
205    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
206    /// True: Full Support, false: No support
207    pub null_ordering_supported: bool,
208    /// Whether ignore nulls is inside the agg or outside
209    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
210    pub ignore_nulls_in_func: bool,
211    /// Whether the NVL2 function is supported
212    pub nvl2_supported: bool,
213
214    // ===== Limit/Fetch =====
215    /// How to output LIMIT clauses
216    pub limit_fetch_style: LimitFetchStyle,
217    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
218    pub limit_is_top: bool,
219    /// Whether limit and fetch allows expressions or just literals
220    pub limit_only_literals: bool,
221
222    // ===== Interval =====
223    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
224    pub single_string_interval: bool,
225    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
226    pub interval_allows_plural_form: bool,
227
228    // ===== CTE =====
229    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
230    pub cte_recursive_keyword_required: bool,
231
232    // ===== VALUES =====
233    /// Whether VALUES can be used as a table source
234    pub values_as_table: bool,
235    /// Wrap derived values in parens (standard but Spark doesn't support)
236    pub wrap_derived_values: bool,
237
238    // ===== TABLESAMPLE =====
239    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
240    pub tablesample_seed_keyword: &'static str,
241    /// Whether parentheses are required around the table sample's expression
242    pub tablesample_requires_parens: bool,
243    /// Whether a table sample clause's size needs to be followed by ROWS keyword
244    pub tablesample_size_is_rows: bool,
245    /// The keyword(s) to use when generating a sample clause
246    pub tablesample_keywords: &'static str,
247    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
248    pub tablesample_with_method: bool,
249    /// Whether the table alias comes after tablesample (Oracle, Hive)
250    pub alias_post_tablesample: bool,
251
252    // ===== Aggregate =====
253    /// Whether aggregate FILTER (WHERE ...) is supported
254    pub aggregate_filter_supported: bool,
255    /// Whether DISTINCT can be followed by multiple args in an AggFunc
256    pub multi_arg_distinct: bool,
257    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
258    pub quantified_no_paren_space: bool,
259    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
260    pub supports_median: bool,
261
262    // ===== SELECT =====
263    /// Whether SELECT ... INTO is supported
264    pub supports_select_into: bool,
265    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
266    pub locking_reads_supported: bool,
267
268    // ===== Table/Join =====
269    /// Whether a table is allowed to be renamed with a db
270    pub rename_table_with_db: bool,
271    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
272    pub semi_anti_join_with_side: bool,
273    /// Whether named columns are allowed in table aliases
274    pub supports_table_alias_columns: bool,
275    /// Whether join hints should be generated
276    pub join_hints: bool,
277    /// Whether table hints should be generated
278    pub table_hints: bool,
279    /// Whether query hints should be generated
280    pub query_hints: bool,
281    /// What kind of separator to use for query hints
282    pub query_hint_sep: &'static str,
283    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
284    pub supports_column_join_marks: bool,
285
286    // ===== DDL =====
287    /// Whether CREATE INDEX USING method should have no space before column parens
288    /// true: `USING btree(col)`, false: `USING btree (col)`
289    pub index_using_no_space: bool,
290    /// Whether UNLOGGED tables can be created
291    pub supports_unlogged_tables: bool,
292    /// Whether CREATE TABLE LIKE statement is supported
293    pub supports_create_table_like: bool,
294    /// Whether the LikeProperty needs to be inside the schema clause
295    pub like_property_inside_schema: bool,
296    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
297    pub alter_table_include_column_keyword: bool,
298    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
299    pub supports_table_copy: bool,
300    /// The syntax to use when altering the type of a column
301    pub alter_set_type: &'static str,
302    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
303    pub alter_set_wrapped: bool,
304
305    // ===== Timestamp/Timezone =====
306    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
307    pub tz_to_with_time_zone: bool,
308    /// Whether CONVERT_TIMEZONE() is supported
309    pub supports_convert_timezone: bool,
310
311    // ===== JSON =====
312    /// Whether the JSON extraction operators expect a value of type JSON
313    pub json_type_required_for_extraction: bool,
314    /// Whether bracketed keys like ["foo"] are supported in JSON paths
315    pub json_path_bracketed_key_supported: bool,
316    /// Whether to escape keys using single quotes in JSON paths
317    pub json_path_single_quote_escape: bool,
318    /// Whether to quote the generated expression of JsonPath
319    pub quote_json_path: bool,
320    /// What delimiter to use for separating JSON key/value pairs
321    pub json_key_value_pair_sep: &'static str,
322
323    // ===== COPY =====
324    /// Whether parameters from COPY statement are wrapped in parentheses
325    pub copy_params_are_wrapped: bool,
326    /// Whether values of params are set with "=" token or empty space
327    pub copy_params_eq_required: bool,
328    /// Whether COPY statement has INTO keyword
329    pub copy_has_into_keyword: bool,
330
331    // ===== Window functions =====
332    /// Whether EXCLUDE in window specification is supported
333    pub supports_window_exclude: bool,
334    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
335    pub unnest_with_ordinality: bool,
336    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
337    /// Exasol uses lowercase for these specific keywords
338    pub lowercase_window_frame_keywords: bool,
339    /// Whether to normalize single-bound window frames to BETWEEN form
340    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
341    pub normalize_window_frame_between: bool,
342
343    // ===== Array =====
344    /// Whether ARRAY_CONCAT can be generated with varlen args
345    pub array_concat_is_var_len: bool,
346    /// Whether exp.ArraySize should generate the dimension arg too
347    /// None -> Doesn't support, false -> optional, true -> required
348    pub array_size_dim_required: Option<bool>,
349    /// Whether any(f(x) for x in array) can be implemented
350    pub can_implement_array_any: bool,
351    /// Function used for array size
352    pub array_size_name: &'static str,
353
354    // ===== BETWEEN =====
355    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
356    pub supports_between_flags: bool,
357
358    // ===== Boolean =====
359    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
360    pub is_bool_allowed: bool,
361    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
362    pub ensure_bools: bool,
363
364    // ===== EXTRACT =====
365    /// Whether to generate an unquoted value for EXTRACT's date part argument
366    pub extract_allows_quotes: bool,
367    /// Whether to normalize date parts in EXTRACT
368    pub normalize_extract_date_parts: bool,
369
370    // ===== Other features =====
371    /// Whether the conditional TRY(expression) function is supported
372    pub try_supported: bool,
373    /// Whether the UESCAPE syntax in unicode strings is supported
374    pub supports_uescape: bool,
375    /// Whether the function TO_NUMBER is supported
376    pub supports_to_number: bool,
377    /// Whether CONCAT requires >1 arguments
378    pub supports_single_arg_concat: bool,
379    /// Whether LAST_DAY function supports a date part argument
380    pub last_day_supports_date_part: bool,
381    /// Whether a projection can explode into multiple rows
382    pub supports_exploding_projections: bool,
383    /// Whether UNIX_SECONDS(timestamp) is supported
384    pub supports_unix_seconds: bool,
385    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
386    pub supports_like_quantifiers: bool,
387    /// Whether multi-argument DECODE(...) function is supported
388    pub supports_decode_case: bool,
389    /// Whether set op modifiers apply to the outer set op or select
390    pub set_op_modifiers: bool,
391    /// Whether FROM is supported in UPDATE statements
392    pub update_statement_supports_from: bool,
393
394    // ===== COLLATE =====
395    /// Whether COLLATE is a function instead of a binary operator
396    pub collate_is_func: bool,
397
398    // ===== INSERT =====
399    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
400    pub duplicate_key_update_with_set: bool,
401    /// INSERT OVERWRITE TABLE x override
402    pub insert_overwrite: &'static str,
403
404    // ===== RETURNING =====
405    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
406    pub returning_end: bool,
407
408    // ===== MERGE =====
409    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
410    pub matched_by_source: bool,
411
412    // ===== CREATE FUNCTION =====
413    /// Whether create function uses an AS before the RETURN
414    pub create_function_return_as: bool,
415    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
416    pub parameter_default_equals: bool,
417
418    // ===== COMPUTED COLUMN =====
419    /// Whether to include the type of a computed column in the CREATE DDL
420    pub computed_column_with_type: bool,
421
422    // ===== UNPIVOT =====
423    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
424    pub unpivot_aliases_are_identifiers: bool,
425
426    // ===== STAR =====
427    /// The keyword to use when generating a star projection with excluded columns
428    pub star_except: &'static str,
429
430    // ===== HEX =====
431    /// The HEX function name
432    pub hex_func: &'static str,
433
434    // ===== WITH =====
435    /// The keywords to use when prefixing WITH based properties
436    pub with_properties_prefix: &'static str,
437
438    // ===== PAD =====
439    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
440    pub pad_fill_pattern_is_required: bool,
441
442    // ===== INDEX =====
443    /// The string used for creating an index on a table
444    pub index_on: &'static str,
445
446    // ===== GROUPING =====
447    /// The separator for grouping sets and rollups
448    pub groupings_sep: &'static str,
449
450    // ===== STRUCT =====
451    /// Delimiters for STRUCT type
452    pub struct_delimiter: (&'static str, &'static str),
453    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
454    pub struct_curly_brace_notation: bool,
455    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
456    pub array_bracket_only: bool,
457    /// Separator between struct field name and type (": " for Hive, " " for others)
458    pub struct_field_sep: &'static str,
459
460    // ===== EXCEPT/INTERSECT =====
461    /// Whether EXCEPT and INTERSECT operations can return duplicates
462    pub except_intersect_support_all_clause: bool,
463
464    // ===== PARAMETERS/PLACEHOLDERS =====
465    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
466    pub parameter_token: &'static str,
467    /// Named placeholder token (: for most, % for PostgreSQL)
468    pub named_placeholder_token: &'static str,
469
470    // ===== DATA TYPES =====
471    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
472    pub data_type_specifiers_allowed: bool,
473
474    // ===== COMMENT =====
475    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
476    /// StarRocks and Doris use naked COMMENT syntax without `=`
477    pub schema_comment_with_eq: bool,
478}
479
480impl Default for GeneratorConfig {
481    fn default() -> Self {
482        Self {
483            // ===== Basic formatting =====
484            pretty: false,
485            indent: "  ".to_string(),
486            max_text_width: 80,
487            identifier_quote: '"',
488            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
489            uppercase_keywords: true,
490            normalize_identifiers: false,
491            dialect: None,
492            source_dialect: None,
493            normalize_functions: NormalizeFunctions::Upper,
494            string_escape: '\'',
495            case_sensitive_identifiers: false,
496            identifiers_can_start_with_digit: false,
497            always_quote_identifiers: false,
498            not_in_style: NotInStyle::Prefix,
499
500            // ===== Null handling =====
501            null_ordering_supported: true,
502            ignore_nulls_in_func: false,
503            nvl2_supported: true,
504
505            // ===== Limit/Fetch =====
506            limit_fetch_style: LimitFetchStyle::Limit,
507            limit_is_top: false,
508            limit_only_literals: false,
509
510            // ===== Interval =====
511            single_string_interval: false,
512            interval_allows_plural_form: true,
513
514            // ===== CTE =====
515            cte_recursive_keyword_required: true,
516
517            // ===== VALUES =====
518            values_as_table: true,
519            wrap_derived_values: true,
520
521            // ===== TABLESAMPLE =====
522            tablesample_seed_keyword: "SEED",
523            tablesample_requires_parens: true,
524            tablesample_size_is_rows: true,
525            tablesample_keywords: "TABLESAMPLE",
526            tablesample_with_method: true,
527            alias_post_tablesample: false,
528
529            // ===== Aggregate =====
530            aggregate_filter_supported: true,
531            multi_arg_distinct: true,
532            quantified_no_paren_space: false,
533            supports_median: true,
534
535            // ===== SELECT =====
536            supports_select_into: false,
537            locking_reads_supported: true,
538
539            // ===== Table/Join =====
540            rename_table_with_db: true,
541            semi_anti_join_with_side: true,
542            supports_table_alias_columns: true,
543            join_hints: true,
544            table_hints: true,
545            query_hints: true,
546            query_hint_sep: ", ",
547            supports_column_join_marks: false,
548
549            // ===== DDL =====
550            index_using_no_space: false,
551            supports_unlogged_tables: false,
552            supports_create_table_like: true,
553            like_property_inside_schema: false,
554            alter_table_include_column_keyword: true,
555            supports_table_copy: true,
556            alter_set_type: "SET DATA TYPE",
557            alter_set_wrapped: false,
558
559            // ===== Timestamp/Timezone =====
560            tz_to_with_time_zone: false,
561            supports_convert_timezone: false,
562
563            // ===== JSON =====
564            json_type_required_for_extraction: false,
565            json_path_bracketed_key_supported: true,
566            json_path_single_quote_escape: false,
567            quote_json_path: true,
568            json_key_value_pair_sep: ":",
569
570            // ===== COPY =====
571            copy_params_are_wrapped: true,
572            copy_params_eq_required: false,
573            copy_has_into_keyword: true,
574
575            // ===== Window functions =====
576            supports_window_exclude: false,
577            unnest_with_ordinality: true,
578            lowercase_window_frame_keywords: false,
579            normalize_window_frame_between: false,
580
581            // ===== Array =====
582            array_concat_is_var_len: true,
583            array_size_dim_required: None,
584            can_implement_array_any: false,
585            array_size_name: "ARRAY_LENGTH",
586
587            // ===== BETWEEN =====
588            supports_between_flags: false,
589
590            // ===== Boolean =====
591            is_bool_allowed: true,
592            ensure_bools: false,
593
594            // ===== EXTRACT =====
595            extract_allows_quotes: true,
596            normalize_extract_date_parts: false,
597
598            // ===== Other features =====
599            try_supported: true,
600            supports_uescape: true,
601            supports_to_number: true,
602            supports_single_arg_concat: true,
603            last_day_supports_date_part: true,
604            supports_exploding_projections: true,
605            supports_unix_seconds: false,
606            supports_like_quantifiers: true,
607            supports_decode_case: true,
608            set_op_modifiers: true,
609            update_statement_supports_from: true,
610
611            // ===== COLLATE =====
612            collate_is_func: false,
613
614            // ===== INSERT =====
615            duplicate_key_update_with_set: true,
616            insert_overwrite: " OVERWRITE TABLE",
617
618            // ===== RETURNING =====
619            returning_end: true,
620
621            // ===== MERGE =====
622            matched_by_source: true,
623
624            // ===== CREATE FUNCTION =====
625            create_function_return_as: true,
626            parameter_default_equals: false,
627
628            // ===== COMPUTED COLUMN =====
629            computed_column_with_type: true,
630
631            // ===== UNPIVOT =====
632            unpivot_aliases_are_identifiers: true,
633
634            // ===== STAR =====
635            star_except: "EXCEPT",
636
637            // ===== HEX =====
638            hex_func: "HEX",
639
640            // ===== WITH =====
641            with_properties_prefix: "WITH",
642
643            // ===== PAD =====
644            pad_fill_pattern_is_required: false,
645
646            // ===== INDEX =====
647            index_on: "ON",
648
649            // ===== GROUPING =====
650            groupings_sep: ",",
651
652            // ===== STRUCT =====
653            struct_delimiter: ("<", ">"),
654            struct_curly_brace_notation: false,
655            array_bracket_only: false,
656            struct_field_sep: " ",
657
658            // ===== EXCEPT/INTERSECT =====
659            except_intersect_support_all_clause: true,
660
661            // ===== PARAMETERS/PLACEHOLDERS =====
662            parameter_token: "@",
663            named_placeholder_token: ":",
664
665            // ===== DATA TYPES =====
666            data_type_specifiers_allowed: false,
667
668            // ===== COMMENT =====
669            schema_comment_with_eq: true,
670        }
671    }
672}
673
674/// SQL reserved keywords that require quoting when used as identifiers
675/// Based on ANSI SQL standards and common dialect-specific reserved words
676mod reserved_keywords {
677    use std::collections::HashSet;
678    use std::sync::LazyLock;
679
680    /// Standard SQL reserved keywords (ANSI SQL:2016)
681    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
682        [
683            "all",
684            "alter",
685            "and",
686            "any",
687            "array",
688            "as",
689            "asc",
690            "at",
691            "authorization",
692            "begin",
693            "between",
694            "both",
695            "by",
696            "case",
697            "cast",
698            "check",
699            "collate",
700            "column",
701            "commit",
702            "constraint",
703            "create",
704            "cross",
705            "cube",
706            "current",
707            "current_date",
708            "current_time",
709            "current_timestamp",
710            "current_user",
711            "default",
712            "delete",
713            "desc",
714            "distinct",
715            "drop",
716            "else",
717            "end",
718            "escape",
719            "except",
720            "execute",
721            "exists",
722            "external",
723            "false",
724            "fetch",
725            "filter",
726            "for",
727            "foreign",
728            "from",
729            "full",
730            "function",
731            "grant",
732            "group",
733            "grouping",
734            "having",
735            "if",
736            "in",
737            "index",
738            "inner",
739            "insert",
740            "intersect",
741            "interval",
742            "into",
743            "is",
744            "join",
745            "key",
746            "leading",
747            "left",
748            "like",
749            "limit",
750            "local",
751            "localtime",
752            "localtimestamp",
753            "match",
754            "merge",
755            "natural",
756            "no",
757            "not",
758            "null",
759            "of",
760            "offset",
761            "on",
762            "only",
763            "or",
764            "order",
765            "outer",
766            "over",
767            "partition",
768            "primary",
769            "procedure",
770            "range",
771            "references",
772            "right",
773            "rollback",
774            "rollup",
775            "row",
776            "rows",
777            "select",
778            "session_user",
779            "set",
780            "some",
781            "table",
782            "tablesample",
783            "then",
784            "to",
785            "trailing",
786            "true",
787            "truncate",
788            "union",
789            "unique",
790            "unknown",
791            "update",
792            "user",
793            "using",
794            "values",
795            "view",
796            "when",
797            "where",
798            "window",
799            "with",
800        ]
801        .into_iter()
802        .collect()
803    });
804
805    /// BigQuery-specific reserved keywords
806    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
807    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
808        let mut set = SQL_RESERVED.clone();
809        set.extend([
810            "assert_rows_modified",
811            "at",
812            "contains",
813            "cube",
814            "current",
815            "define",
816            "enum",
817            "escape",
818            "exclude",
819            "following",
820            "for",
821            "groups",
822            "hash",
823            "ignore",
824            "lateral",
825            "lookup",
826            "new",
827            "no",
828            "nulls",
829            "of",
830            "over",
831            "preceding",
832            "proto",
833            "qualify",
834            "recursive",
835            "respect",
836            "struct",
837            "tablesample",
838            "treat",
839            "unbounded",
840            "unnest",
841            "window",
842            "within",
843        ]);
844        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
845        set.remove("grant");
846        set.remove("key");
847        set.remove("index");
848        set.remove("values");
849        set.remove("table");
850        set
851    });
852
853    /// MySQL-specific reserved keywords
854    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
855        let mut set = SQL_RESERVED.clone();
856        set.extend([
857            "accessible",
858            "add",
859            "analyze",
860            "asensitive",
861            "before",
862            "bigint",
863            "binary",
864            "blob",
865            "call",
866            "cascade",
867            "change",
868            "char",
869            "character",
870            "condition",
871            "continue",
872            "convert",
873            "current_date",
874            "current_time",
875            "current_timestamp",
876            "current_user",
877            "cursor",
878            "database",
879            "databases",
880            "day_hour",
881            "day_microsecond",
882            "day_minute",
883            "day_second",
884            "dec",
885            "decimal",
886            "declare",
887            "delayed",
888            "describe",
889            "deterministic",
890            "distinctrow",
891            "div",
892            "double",
893            "dual",
894            "each",
895            "elseif",
896            "enclosed",
897            "escaped",
898            "exit",
899            "explain",
900            "float",
901            "float4",
902            "float8",
903            "force",
904            "get",
905            "high_priority",
906            "hour_microsecond",
907            "hour_minute",
908            "hour_second",
909            "ignore",
910            "infile",
911            "inout",
912            "insensitive",
913            "int",
914            "int1",
915            "int2",
916            "int3",
917            "int4",
918            "int8",
919            "integer",
920            "iterate",
921            "keys",
922            "kill",
923            "leave",
924            "linear",
925            "lines",
926            "load",
927            "lock",
928            "long",
929            "longblob",
930            "longtext",
931            "loop",
932            "low_priority",
933            "master_ssl_verify_server_cert",
934            "maxvalue",
935            "mediumblob",
936            "mediumint",
937            "mediumtext",
938            "middleint",
939            "minute_microsecond",
940            "minute_second",
941            "mod",
942            "modifies",
943            "no_write_to_binlog",
944            "numeric",
945            "optimize",
946            "option",
947            "optionally",
948            "out",
949            "outfile",
950            "precision",
951            "purge",
952            "read",
953            "reads",
954            "real",
955            "regexp",
956            "release",
957            "rename",
958            "repeat",
959            "replace",
960            "require",
961            "resignal",
962            "restrict",
963            "return",
964            "revoke",
965            "rlike",
966            "schema",
967            "schemas",
968            "second_microsecond",
969            "sensitive",
970            "separator",
971            "show",
972            "signal",
973            "smallint",
974            "spatial",
975            "specific",
976            "sql",
977            "sql_big_result",
978            "sql_calc_found_rows",
979            "sql_small_result",
980            "sqlexception",
981            "sqlstate",
982            "sqlwarning",
983            "ssl",
984            "starting",
985            "straight_join",
986            "terminated",
987            "text",
988            "tinyblob",
989            "tinyint",
990            "tinytext",
991            "trigger",
992            "undo",
993            "unlock",
994            "unsigned",
995            "usage",
996            "utc_date",
997            "utc_time",
998            "utc_timestamp",
999            "varbinary",
1000            "varchar",
1001            "varcharacter",
1002            "varying",
1003            "while",
1004            "write",
1005            "xor",
1006            "year_month",
1007            "zerofill",
1008        ]);
1009        set.remove("table");
1010        set
1011    });
1012
1013    /// Doris-specific reserved keywords
1014    /// Extends MySQL reserved with additional Doris-specific words
1015    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1016        let mut set = MYSQL_RESERVED.clone();
1017        set.extend([
1018            "aggregate",
1019            "anti",
1020            "array",
1021            "backend",
1022            "backup",
1023            "begin",
1024            "bitmap",
1025            "boolean",
1026            "broker",
1027            "buckets",
1028            "cached",
1029            "cancel",
1030            "cast",
1031            "catalog",
1032            "charset",
1033            "cluster",
1034            "collation",
1035            "columns",
1036            "comment",
1037            "commit",
1038            "config",
1039            "connection",
1040            "count",
1041            "current",
1042            "data",
1043            "date",
1044            "datetime",
1045            "day",
1046            "deferred",
1047            "distributed",
1048            "dynamic",
1049            "enable",
1050            "end",
1051            "events",
1052            "export",
1053            "external",
1054            "fields",
1055            "first",
1056            "follower",
1057            "format",
1058            "free",
1059            "frontend",
1060            "full",
1061            "functions",
1062            "global",
1063            "grants",
1064            "hash",
1065            "help",
1066            "hour",
1067            "install",
1068            "intermediate",
1069            "json",
1070            "label",
1071            "last",
1072            "less",
1073            "level",
1074            "link",
1075            "local",
1076            "location",
1077            "max",
1078            "merge",
1079            "min",
1080            "minute",
1081            "modify",
1082            "month",
1083            "name",
1084            "names",
1085            "negative",
1086            "nulls",
1087            "observer",
1088            "offset",
1089            "only",
1090            "open",
1091            "overwrite",
1092            "password",
1093            "path",
1094            "plan",
1095            "plugin",
1096            "plugins",
1097            "policy",
1098            "process",
1099            "properties",
1100            "property",
1101            "query",
1102            "quota",
1103            "recover",
1104            "refresh",
1105            "repair",
1106            "replica",
1107            "repository",
1108            "resource",
1109            "restore",
1110            "resume",
1111            "role",
1112            "roles",
1113            "rollback",
1114            "rollup",
1115            "routine",
1116            "sample",
1117            "second",
1118            "semi",
1119            "session",
1120            "signed",
1121            "snapshot",
1122            "start",
1123            "stats",
1124            "status",
1125            "stop",
1126            "stream",
1127            "string",
1128            "sum",
1129            "tables",
1130            "tablet",
1131            "temporary",
1132            "text",
1133            "timestamp",
1134            "transaction",
1135            "trash",
1136            "trim",
1137            "truncate",
1138            "type",
1139            "user",
1140            "value",
1141            "variables",
1142            "verbose",
1143            "version",
1144            "view",
1145            "warnings",
1146            "week",
1147            "work",
1148            "year",
1149        ]);
1150        set
1151    });
1152
1153    /// PostgreSQL-specific reserved keywords
1154    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1155        let mut set = SQL_RESERVED.clone();
1156        set.extend([
1157            "analyse",
1158            "analyze",
1159            "asymmetric",
1160            "binary",
1161            "collation",
1162            "concurrently",
1163            "current_catalog",
1164            "current_role",
1165            "current_schema",
1166            "deferrable",
1167            "do",
1168            "freeze",
1169            "ilike",
1170            "initially",
1171            "isnull",
1172            "lateral",
1173            "notnull",
1174            "placing",
1175            "returning",
1176            "similar",
1177            "symmetric",
1178            "variadic",
1179            "verbose",
1180        ]);
1181        // PostgreSQL doesn't require quoting for these keywords
1182        set.remove("default");
1183        set.remove("interval");
1184        set.remove("match");
1185        set.remove("offset");
1186        set.remove("table");
1187        set
1188    });
1189
1190    /// Redshift-specific reserved keywords
1191    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1192    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1193    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1194        [
1195            "aes128",
1196            "aes256",
1197            "all",
1198            "allowoverwrite",
1199            "analyse",
1200            "analyze",
1201            "and",
1202            "any",
1203            "array",
1204            "as",
1205            "asc",
1206            "authorization",
1207            "az64",
1208            "backup",
1209            "between",
1210            "binary",
1211            "blanksasnull",
1212            "both",
1213            "bytedict",
1214            "bzip2",
1215            "case",
1216            "cast",
1217            "check",
1218            "collate",
1219            "column",
1220            "constraint",
1221            "create",
1222            "credentials",
1223            "cross",
1224            "current_date",
1225            "current_time",
1226            "current_timestamp",
1227            "current_user",
1228            "current_user_id",
1229            "default",
1230            "deferrable",
1231            "deflate",
1232            "defrag",
1233            "delta",
1234            "delta32k",
1235            "desc",
1236            "disable",
1237            "distinct",
1238            "do",
1239            "else",
1240            "emptyasnull",
1241            "enable",
1242            "encode",
1243            "encrypt",
1244            "encryption",
1245            "end",
1246            "except",
1247            "explicit",
1248            "false",
1249            "for",
1250            "foreign",
1251            "freeze",
1252            "from",
1253            "full",
1254            "globaldict256",
1255            "globaldict64k",
1256            "grant",
1257            "group",
1258            "gzip",
1259            "having",
1260            "identity",
1261            "ignore",
1262            "ilike",
1263            "in",
1264            "initially",
1265            "inner",
1266            "intersect",
1267            "interval",
1268            "into",
1269            "is",
1270            "isnull",
1271            "join",
1272            "leading",
1273            "left",
1274            "like",
1275            "limit",
1276            "localtime",
1277            "localtimestamp",
1278            "lun",
1279            "luns",
1280            "lzo",
1281            "lzop",
1282            "minus",
1283            "mostly16",
1284            "mostly32",
1285            "mostly8",
1286            "natural",
1287            "new",
1288            "not",
1289            "notnull",
1290            "null",
1291            "nulls",
1292            "off",
1293            "offline",
1294            "offset",
1295            "oid",
1296            "old",
1297            "on",
1298            "only",
1299            "open",
1300            "or",
1301            "order",
1302            "outer",
1303            "overlaps",
1304            "parallel",
1305            "partition",
1306            "percent",
1307            "permissions",
1308            "pivot",
1309            "placing",
1310            "primary",
1311            "raw",
1312            "readratio",
1313            "recover",
1314            "references",
1315            "rejectlog",
1316            "resort",
1317            "respect",
1318            "restore",
1319            "right",
1320            "select",
1321            "session_user",
1322            "similar",
1323            "snapshot",
1324            "some",
1325            "sysdate",
1326            "system",
1327            "table",
1328            "tag",
1329            "tdes",
1330            "text255",
1331            "text32k",
1332            "then",
1333            "timestamp",
1334            "to",
1335            "top",
1336            "trailing",
1337            "true",
1338            "truncatecolumns",
1339            "type",
1340            "union",
1341            "unique",
1342            "unnest",
1343            "unpivot",
1344            "user",
1345            "using",
1346            "verbose",
1347            "wallet",
1348            "when",
1349            "where",
1350            "with",
1351            "without",
1352        ]
1353        .into_iter()
1354        .collect()
1355    });
1356
1357    /// DuckDB-specific reserved keywords
1358    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1359        let mut set = POSTGRES_RESERVED.clone();
1360        set.extend([
1361            "anti",
1362            "asof",
1363            "columns",
1364            "describe",
1365            "groups",
1366            "macro",
1367            "pivot",
1368            "pivot_longer",
1369            "pivot_wider",
1370            "qualify",
1371            "replace",
1372            "respect",
1373            "semi",
1374            "show",
1375            "table",
1376            "unpivot",
1377        ]);
1378        set.remove("at");
1379        set.remove("key");
1380        set.remove("row");
1381        set
1382    });
1383
1384    /// Presto/Trino/Athena-specific reserved keywords
1385    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1386        let mut set = SQL_RESERVED.clone();
1387        set.extend([
1388            "alter",
1389            "and",
1390            "as",
1391            "between",
1392            "by",
1393            "case",
1394            "cast",
1395            "constraint",
1396            "create",
1397            "cross",
1398            "cube",
1399            "current_catalog",
1400            "current_date",
1401            "current_path",
1402            "current_role",
1403            "current_schema",
1404            "current_time",
1405            "current_timestamp",
1406            "current_user",
1407            "deallocate",
1408            "delete",
1409            "describe",
1410            "distinct",
1411            "drop",
1412            "else",
1413            "end",
1414            "escape",
1415            "except",
1416            "execute",
1417            "exists",
1418            "extract",
1419            "false",
1420            "for",
1421            "from",
1422            "full",
1423            "group",
1424            "grouping",
1425            "having",
1426            "in",
1427            "inner",
1428            "insert",
1429            "intersect",
1430            "into",
1431            "is",
1432            "join",
1433            "json_array",
1434            "json_exists",
1435            "json_object",
1436            "json_query",
1437            "json_table",
1438            "json_value",
1439            "left",
1440            "like",
1441            "listagg",
1442            "localtime",
1443            "localtimestamp",
1444            "natural",
1445            "normalize",
1446            "not",
1447            "null",
1448            "on",
1449            "or",
1450            "order",
1451            "outer",
1452            "prepare",
1453            "recursive",
1454            "right",
1455            "rollup",
1456            "select",
1457            "skip",
1458            "table",
1459            "then",
1460            "trim",
1461            "true",
1462            "uescape",
1463            "union",
1464            "unnest",
1465            "using",
1466            "values",
1467            "when",
1468            "where",
1469            "with",
1470        ]);
1471        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1472        set.remove("key");
1473        set
1474    });
1475
1476    /// StarRocks-specific reserved keywords
1477    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1478    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1479        [
1480            "add",
1481            "all",
1482            "alter",
1483            "analyze",
1484            "and",
1485            "array",
1486            "as",
1487            "asc",
1488            "between",
1489            "bigint",
1490            "bitmap",
1491            "both",
1492            "by",
1493            "case",
1494            "char",
1495            "character",
1496            "check",
1497            "collate",
1498            "column",
1499            "compaction",
1500            "convert",
1501            "create",
1502            "cross",
1503            "cube",
1504            "current_date",
1505            "current_role",
1506            "current_time",
1507            "current_timestamp",
1508            "current_user",
1509            "database",
1510            "databases",
1511            "decimal",
1512            "decimalv2",
1513            "decimal32",
1514            "decimal64",
1515            "decimal128",
1516            "default",
1517            "deferred",
1518            "delete",
1519            "dense_rank",
1520            "desc",
1521            "describe",
1522            "distinct",
1523            "double",
1524            "drop",
1525            "dual",
1526            "else",
1527            "except",
1528            "exists",
1529            "explain",
1530            "false",
1531            "first_value",
1532            "float",
1533            "for",
1534            "force",
1535            "from",
1536            "full",
1537            "function",
1538            "grant",
1539            "group",
1540            "grouping",
1541            "grouping_id",
1542            "groups",
1543            "having",
1544            "hll",
1545            "host",
1546            "if",
1547            "ignore",
1548            "immediate",
1549            "in",
1550            "index",
1551            "infile",
1552            "inner",
1553            "insert",
1554            "int",
1555            "integer",
1556            "intersect",
1557            "into",
1558            "is",
1559            "join",
1560            "json",
1561            "key",
1562            "keys",
1563            "kill",
1564            "lag",
1565            "largeint",
1566            "last_value",
1567            "lateral",
1568            "lead",
1569            "left",
1570            "like",
1571            "limit",
1572            "load",
1573            "localtime",
1574            "localtimestamp",
1575            "maxvalue",
1576            "minus",
1577            "mod",
1578            "not",
1579            "ntile",
1580            "null",
1581            "on",
1582            "or",
1583            "order",
1584            "outer",
1585            "outfile",
1586            "over",
1587            "partition",
1588            "percentile",
1589            "primary",
1590            "procedure",
1591            "qualify",
1592            "range",
1593            "rank",
1594            "read",
1595            "regexp",
1596            "release",
1597            "rename",
1598            "replace",
1599            "revoke",
1600            "right",
1601            "rlike",
1602            "row",
1603            "row_number",
1604            "rows",
1605            "schema",
1606            "schemas",
1607            "select",
1608            "set",
1609            "set_var",
1610            "show",
1611            "smallint",
1612            "system",
1613            "table",
1614            "terminated",
1615            "text",
1616            "then",
1617            "tinyint",
1618            "to",
1619            "true",
1620            "union",
1621            "unique",
1622            "unsigned",
1623            "update",
1624            "use",
1625            "using",
1626            "values",
1627            "varchar",
1628            "when",
1629            "where",
1630            "with",
1631        ]
1632        .into_iter()
1633        .collect()
1634    });
1635
1636    /// SingleStore-specific reserved keywords
1637    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1638    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1639        let mut set = MYSQL_RESERVED.clone();
1640        set.extend([
1641            // Additional SingleStore reserved keywords from Python sqlglot
1642            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1643            "abs",
1644            "account",
1645            "acos",
1646            "adddate",
1647            "addtime",
1648            "admin",
1649            "aes_decrypt",
1650            "aes_encrypt",
1651            "aggregate",
1652            "aggregates",
1653            "aggregator",
1654            "anti_join",
1655            "any_value",
1656            "approx_count_distinct",
1657            "approx_percentile",
1658            "arrange",
1659            "arrangement",
1660            "asin",
1661            "atan",
1662            "atan2",
1663            "attach",
1664            "autostats",
1665            "avro",
1666            "background",
1667            "backup",
1668            "batch",
1669            "batches",
1670            "boot_strapping",
1671            "ceil",
1672            "ceiling",
1673            "coercibility",
1674            "columnar",
1675            "columnstore",
1676            "compile",
1677            "concurrent",
1678            "connection_id",
1679            "cos",
1680            "cot",
1681            "current_security_groups",
1682            "current_security_roles",
1683            "dayname",
1684            "dayofmonth",
1685            "dayofweek",
1686            "dayofyear",
1687            "degrees",
1688            "dot_product",
1689            "dump",
1690            "durability",
1691            "earliest",
1692            "echo",
1693            "election",
1694            "euclidean_distance",
1695            "exp",
1696            "extractor",
1697            "extractors",
1698            "floor",
1699            "foreground",
1700            "found_rows",
1701            "from_base64",
1702            "from_days",
1703            "from_unixtime",
1704            "fs",
1705            "fulltext",
1706            "gc",
1707            "gcs",
1708            "geography",
1709            "geography_area",
1710            "geography_contains",
1711            "geography_distance",
1712            "geography_intersects",
1713            "geography_latitude",
1714            "geography_length",
1715            "geography_longitude",
1716            "geographypoint",
1717            "geography_point",
1718            "geography_within_distance",
1719            "geometry",
1720            "geometry_area",
1721            "geometry_contains",
1722            "geometry_distance",
1723            "geometry_filter",
1724            "geometry_intersects",
1725            "geometry_length",
1726            "geometrypoint",
1727            "geometry_point",
1728            "geometry_within_distance",
1729            "geometry_x",
1730            "geometry_y",
1731            "greatest",
1732            "groups",
1733            "group_concat",
1734            "gzip",
1735            "hdfs",
1736            "hex",
1737            "highlight",
1738            "ifnull",
1739            "ilike",
1740            "inet_aton",
1741            "inet_ntoa",
1742            "inet6_aton",
1743            "inet6_ntoa",
1744            "initcap",
1745            "instr",
1746            "interpreter_mode",
1747            "isnull",
1748            "json",
1749            "json_agg",
1750            "json_array_contains_double",
1751            "json_array_contains_json",
1752            "json_array_contains_string",
1753            "json_delete_key",
1754            "json_extract_double",
1755            "json_extract_json",
1756            "json_extract_string",
1757            "json_extract_bigint",
1758            "json_get_type",
1759            "json_length",
1760            "json_set_double",
1761            "json_set_json",
1762            "json_set_string",
1763            "kafka",
1764            "lag",
1765            "last_day",
1766            "last_insert_id",
1767            "latest",
1768            "lcase",
1769            "lead",
1770            "leaf",
1771            "least",
1772            "leaves",
1773            "length",
1774            "license",
1775            "links",
1776            "llvm",
1777            "ln",
1778            "load",
1779            "locate",
1780            "log",
1781            "log10",
1782            "log2",
1783            "lpad",
1784            "lz4",
1785            "management",
1786            "match",
1787            "mbc",
1788            "md5",
1789            "median",
1790            "memsql",
1791            "memsql_deserialize",
1792            "memsql_serialize",
1793            "metadata",
1794            "microsecond",
1795            "minute",
1796            "model",
1797            "monthname",
1798            "months_between",
1799            "mpl",
1800            "namespace",
1801            "node",
1802            "noparam",
1803            "now",
1804            "nth_value",
1805            "ntile",
1806            "nullcols",
1807            "nullif",
1808            "object",
1809            "octet_length",
1810            "offsets",
1811            "online",
1812            "optimizer",
1813            "orphan",
1814            "parquet",
1815            "partitions",
1816            "pause",
1817            "percentile_cont",
1818            "percentile_disc",
1819            "periodic",
1820            "persisted",
1821            "pi",
1822            "pipeline",
1823            "pipelines",
1824            "plancache",
1825            "plugins",
1826            "pool",
1827            "pools",
1828            "pow",
1829            "power",
1830            "process",
1831            "processlist",
1832            "profile",
1833            "profiles",
1834            "quarter",
1835            "queries",
1836            "query",
1837            "radians",
1838            "rand",
1839            "record",
1840            "reduce",
1841            "redundancy",
1842            "regexp_match",
1843            "regexp_substr",
1844            "remote",
1845            "replication",
1846            "resource",
1847            "resource_pool",
1848            "restore",
1849            "retry",
1850            "role",
1851            "roles",
1852            "round",
1853            "rpad",
1854            "rtrim",
1855            "running",
1856            "s3",
1857            "scalar",
1858            "sec_to_time",
1859            "second",
1860            "security_lists_intersect",
1861            "semi_join",
1862            "sha",
1863            "sha1",
1864            "sha2",
1865            "shard",
1866            "sharded",
1867            "sharded_id",
1868            "sigmoid",
1869            "sign",
1870            "sin",
1871            "skip",
1872            "sleep",
1873            "snapshot",
1874            "soname",
1875            "sparse",
1876            "spatial_check_index",
1877            "split",
1878            "sqrt",
1879            "standalone",
1880            "std",
1881            "stddev",
1882            "stddev_pop",
1883            "stddev_samp",
1884            "stop",
1885            "str_to_date",
1886            "subdate",
1887            "substr",
1888            "substring_index",
1889            "success",
1890            "synchronize",
1891            "table_checksum",
1892            "tan",
1893            "task",
1894            "timediff",
1895            "time_bucket",
1896            "time_format",
1897            "time_to_sec",
1898            "timestampadd",
1899            "timestampdiff",
1900            "to_base64",
1901            "to_char",
1902            "to_date",
1903            "to_days",
1904            "to_json",
1905            "to_number",
1906            "to_seconds",
1907            "to_timestamp",
1908            "tracelogs",
1909            "transform",
1910            "trim",
1911            "trunc",
1912            "truncate",
1913            "ucase",
1914            "unhex",
1915            "unix_timestamp",
1916            "utc_date",
1917            "utc_time",
1918            "utc_timestamp",
1919            "vacuum",
1920            "variance",
1921            "var_pop",
1922            "var_samp",
1923            "vector_sub",
1924            "voting",
1925            "week",
1926            "weekday",
1927            "weekofyear",
1928            "workload",
1929            "year",
1930        ]);
1931        // Remove "all" because ORDER BY ALL needs ALL unquoted
1932        set.remove("all");
1933        set
1934    });
1935
1936    /// SQLite-specific reserved keywords
1937    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1938    /// Reference: https://www.sqlite.org/lang_keywords.html
1939    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1940        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1941        [
1942            "abort",
1943            "action",
1944            "add",
1945            "after",
1946            "all",
1947            "alter",
1948            "always",
1949            "analyze",
1950            "and",
1951            "as",
1952            "asc",
1953            "attach",
1954            "autoincrement",
1955            "before",
1956            "begin",
1957            "between",
1958            "by",
1959            "cascade",
1960            "case",
1961            "cast",
1962            "check",
1963            "collate",
1964            "column",
1965            "commit",
1966            "conflict",
1967            "constraint",
1968            "create",
1969            "cross",
1970            "current",
1971            "current_date",
1972            "current_time",
1973            "current_timestamp",
1974            "database",
1975            "default",
1976            "deferrable",
1977            "deferred",
1978            "delete",
1979            "desc",
1980            "detach",
1981            "distinct",
1982            "do",
1983            "drop",
1984            "each",
1985            "else",
1986            "end",
1987            "escape",
1988            "except",
1989            "exclude",
1990            "exclusive",
1991            "exists",
1992            "explain",
1993            "fail",
1994            "filter",
1995            "first",
1996            "following",
1997            "for",
1998            "foreign",
1999            "from",
2000            "full",
2001            "generated",
2002            "glob",
2003            "group",
2004            "groups",
2005            "having",
2006            "if",
2007            "ignore",
2008            "immediate",
2009            "in",
2010            "index",
2011            "indexed",
2012            "initially",
2013            "inner",
2014            "insert",
2015            "instead",
2016            "intersect",
2017            "into",
2018            "is",
2019            "isnull",
2020            "join",
2021            "key",
2022            "last",
2023            "left",
2024            "like",
2025            "limit",
2026            "natural",
2027            "no",
2028            "not",
2029            "nothing",
2030            "notnull",
2031            "null",
2032            "nulls",
2033            "of",
2034            "offset",
2035            "on",
2036            "or",
2037            "order",
2038            "others",
2039            "outer",
2040            "partition",
2041            "plan",
2042            "pragma",
2043            "preceding",
2044            "primary",
2045            "query",
2046            "raise",
2047            "range",
2048            "recursive",
2049            "references",
2050            "regexp",
2051            "reindex",
2052            "release",
2053            "rename",
2054            "replace",
2055            "restrict",
2056            "returning",
2057            "right",
2058            "rollback",
2059            "row",
2060            "rows",
2061            "savepoint",
2062            "select",
2063            "set",
2064            "table",
2065            "temp",
2066            "temporary",
2067            "then",
2068            "ties",
2069            "to",
2070            "transaction",
2071            "trigger",
2072            "unbounded",
2073            "union",
2074            "unique",
2075            "update",
2076            "using",
2077            "vacuum",
2078            "values",
2079            "view",
2080            "virtual",
2081            "when",
2082            "where",
2083            "window",
2084            "with",
2085            "without",
2086        ]
2087        .into_iter()
2088        .collect()
2089    });
2090}
2091
2092impl Generator {
2093    /// Create a new generator with the default configuration.
2094    ///
2095    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2096    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2097    /// and no dialect-specific transformations.
2098    pub fn new() -> Self {
2099        Self::with_config(GeneratorConfig::default())
2100    }
2101
2102    /// Create a generator with a custom [`GeneratorConfig`].
2103    ///
2104    /// Use this when you need dialect-specific output, pretty-printing, or other
2105    /// non-default settings.
2106    pub fn with_config(config: GeneratorConfig) -> Self {
2107        Self {
2108            config,
2109            output: String::new(),
2110            indent_level: 0,
2111            athena_hive_context: false,
2112            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2113            merge_strip_qualifiers: Vec::new(),
2114            clickhouse_nullable_depth: 0,
2115        }
2116    }
2117
2118    /// Add column aliases to a query expression for TSQL SELECT INTO.
2119    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2120    /// Recursively processes all SELECT expressions in the query tree.
2121    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2122        match expr {
2123            Expression::Select(mut select) => {
2124                // Add aliases to all select expressions that don't already have them
2125                select.expressions = select
2126                    .expressions
2127                    .into_iter()
2128                    .map(|e| Self::add_alias_to_expression(e))
2129                    .collect();
2130
2131                // Recursively process subqueries in FROM clause
2132                if let Some(ref mut from) = select.from {
2133                    from.expressions = from
2134                        .expressions
2135                        .iter()
2136                        .cloned()
2137                        .map(|e| Self::add_column_aliases_to_query(e))
2138                        .collect();
2139                }
2140
2141                Expression::Select(select)
2142            }
2143            Expression::Subquery(mut sq) => {
2144                sq.this = Self::add_column_aliases_to_query(sq.this);
2145                Expression::Subquery(sq)
2146            }
2147            Expression::Paren(mut p) => {
2148                p.this = Self::add_column_aliases_to_query(p.this);
2149                Expression::Paren(p)
2150            }
2151            // For other expressions (Union, Intersect, etc.), pass through
2152            other => other,
2153        }
2154    }
2155
2156    /// Add an alias to a single select expression if it doesn't already have one.
2157    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2158    fn add_alias_to_expression(expr: Expression) -> Expression {
2159        use crate::expressions::Alias;
2160
2161        match &expr {
2162            // Already aliased - just return it
2163            Expression::Alias(_) => expr,
2164
2165            // Column reference: add alias from column name
2166            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2167                this: expr.clone(),
2168                alias: col.name.clone(),
2169                column_aliases: Vec::new(),
2170                pre_alias_comments: Vec::new(),
2171                trailing_comments: Vec::new(),
2172            })),
2173
2174            // Identifier: add alias from identifier name
2175            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2176                this: expr.clone(),
2177                alias: ident.clone(),
2178                column_aliases: Vec::new(),
2179                pre_alias_comments: Vec::new(),
2180                trailing_comments: Vec::new(),
2181            })),
2182
2183            // Subquery: recursively process and add alias if inner returns a named column
2184            Expression::Subquery(sq) => {
2185                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2186                // Subqueries that are already aliased keep their alias
2187                if sq.alias.is_some() {
2188                    processed
2189                } else {
2190                    // If there's no alias, keep it as-is (let TSQL handle it)
2191                    processed
2192                }
2193            }
2194
2195            // Star expressions (*) - don't alias
2196            Expression::Star(_) => expr,
2197
2198            // For other expressions, don't add an alias
2199            // (function calls, literals, etc. would need explicit aliases anyway)
2200            _ => expr,
2201        }
2202    }
2203
2204    /// Try to evaluate a constant arithmetic expression to a number literal.
2205    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2206    /// otherwise returns the original expression.
2207    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2208        match expr {
2209            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
2210            Expression::Add(op) => {
2211                let left = Self::try_evaluate_constant(&op.left)?;
2212                let right = Self::try_evaluate_constant(&op.right)?;
2213                Some(left + right)
2214            }
2215            Expression::Sub(op) => {
2216                let left = Self::try_evaluate_constant(&op.left)?;
2217                let right = Self::try_evaluate_constant(&op.right)?;
2218                Some(left - right)
2219            }
2220            Expression::Mul(op) => {
2221                let left = Self::try_evaluate_constant(&op.left)?;
2222                let right = Self::try_evaluate_constant(&op.right)?;
2223                Some(left * right)
2224            }
2225            Expression::Div(op) => {
2226                let left = Self::try_evaluate_constant(&op.left)?;
2227                let right = Self::try_evaluate_constant(&op.right)?;
2228                if right != 0 {
2229                    Some(left / right)
2230                } else {
2231                    None
2232                }
2233            }
2234            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2235            _ => None,
2236        }
2237    }
2238
2239    /// Check if an identifier is a reserved keyword for the current dialect
2240    fn is_reserved_keyword(&self, name: &str) -> bool {
2241        use crate::dialects::DialectType;
2242        let lower = name.to_lowercase();
2243        let lower_ref = lower.as_str();
2244
2245        match self.config.dialect {
2246            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2247            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2248                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2249            }
2250            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2251            Some(DialectType::SingleStore) => {
2252                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2253            }
2254            Some(DialectType::StarRocks) => {
2255                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2256            }
2257            Some(DialectType::PostgreSQL)
2258            | Some(DialectType::CockroachDB)
2259            | Some(DialectType::Materialize)
2260            | Some(DialectType::RisingWave) => {
2261                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2262            }
2263            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2264            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2265            // meaning it never quotes identifiers based on reserved word status.
2266            Some(DialectType::Snowflake) => false,
2267            // ClickHouse: don't quote reserved keywords to preserve identity output
2268            Some(DialectType::ClickHouse) => false,
2269            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2270            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2271            Some(DialectType::Teradata) => false,
2272            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2273            Some(DialectType::TSQL)
2274            | Some(DialectType::Fabric)
2275            | Some(DialectType::Oracle)
2276            | Some(DialectType::Spark)
2277            | Some(DialectType::Databricks)
2278            | Some(DialectType::Hive)
2279            | Some(DialectType::Solr) => false,
2280            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2281                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2282            }
2283            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2284            // For Generic dialect or None, don't add extra quoting to preserve identity
2285            Some(DialectType::Generic) | None => false,
2286            // For other dialects, use standard SQL reserved keywords
2287            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2288        }
2289    }
2290
2291    /// Normalize function name based on dialect settings
2292    fn normalize_func_name(&self, name: &str) -> String {
2293        match self.config.normalize_functions {
2294            NormalizeFunctions::Upper => name.to_uppercase(),
2295            NormalizeFunctions::Lower => name.to_lowercase(),
2296            NormalizeFunctions::None => name.to_string(),
2297        }
2298    }
2299
2300    /// Generate a SQL string from an AST expression.
2301    ///
2302    /// This is the primary generation method. It clears any previous internal state,
2303    /// walks the expression tree, and returns the resulting SQL text. The output
2304    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2305    ///
2306    /// The generator can be reused across multiple calls; each call to `generate`
2307    /// resets the internal buffer.
2308    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2309        self.output.clear();
2310        self.generate_expression(expr)?;
2311        Ok(std::mem::take(&mut self.output))
2312    }
2313
2314    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2315    ///
2316    /// This is a static helper that creates a throwaway `Generator` internally.
2317    /// For repeated generation, prefer constructing a `Generator` once and calling
2318    /// [`generate`](Self::generate) on it.
2319    pub fn sql(expr: &Expression) -> Result<String> {
2320        let mut gen = Generator::new();
2321        gen.generate(expr)
2322    }
2323
2324    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2325    ///
2326    /// Produces human-readable output with newlines and indentation. A trailing
2327    /// semicolon is appended automatically if not already present.
2328    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2329        let config = GeneratorConfig {
2330            pretty: true,
2331            ..Default::default()
2332        };
2333        let mut gen = Generator::with_config(config);
2334        let mut sql = gen.generate(expr)?;
2335        // Add semicolon for pretty output
2336        if !sql.ends_with(';') {
2337            sql.push(';');
2338        }
2339        Ok(sql)
2340    }
2341
2342    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2343        match expr {
2344            Expression::Select(select) => self.generate_select(select),
2345            Expression::Union(union) => self.generate_union(union),
2346            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2347            Expression::Except(except) => self.generate_except(except),
2348            Expression::Insert(insert) => self.generate_insert(insert),
2349            Expression::Update(update) => self.generate_update(update),
2350            Expression::Delete(delete) => self.generate_delete(delete),
2351            Expression::Literal(lit) => self.generate_literal(lit),
2352            Expression::Boolean(b) => self.generate_boolean(b),
2353            Expression::Null(_) => {
2354                self.write_keyword("NULL");
2355                Ok(())
2356            }
2357            Expression::Identifier(id) => self.generate_identifier(id),
2358            Expression::Column(col) => self.generate_column(col),
2359            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2360            Expression::Connect(c) => self.generate_connect_expr(c),
2361            Expression::Prior(p) => self.generate_prior(p),
2362            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2363            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2364            Expression::Table(table) => self.generate_table(table),
2365            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2366            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2367            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2368            Expression::Star(star) => self.generate_star(star),
2369            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2370            Expression::Alias(alias) => self.generate_alias(alias),
2371            Expression::Cast(cast) => self.generate_cast(cast),
2372            Expression::Collation(coll) => self.generate_collation(coll),
2373            Expression::Case(case) => self.generate_case(case),
2374            Expression::Function(func) => self.generate_function(func),
2375            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2376            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2377            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2378            Expression::Interval(interval) => self.generate_interval(interval),
2379
2380            // String functions
2381            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2382            Expression::Substring(f) => self.generate_substring(f),
2383            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2384            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2385            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2386            Expression::Trim(f) => self.generate_trim(f),
2387            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2388            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2389            Expression::Replace(f) => self.generate_replace(f),
2390            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2391            Expression::Left(f) => self.generate_left_right("LEFT", f),
2392            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2393            Expression::Repeat(f) => self.generate_repeat(f),
2394            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2395            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2396            Expression::Split(f) => self.generate_split(f),
2397            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2398            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2399            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2400            Expression::Overlay(f) => self.generate_overlay(f),
2401
2402            // Math functions
2403            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2404            Expression::Round(f) => self.generate_round(f),
2405            Expression::Floor(f) => self.generate_floor(f),
2406            Expression::Ceil(f) => self.generate_ceil(f),
2407            Expression::Power(f) => self.generate_power(f),
2408            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2409            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2410            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2411            Expression::Log(f) => self.generate_log(f),
2412            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2413            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2414            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2415            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2416
2417            // Date/time functions
2418            Expression::CurrentDate(_) => {
2419                self.write_keyword("CURRENT_DATE");
2420                Ok(())
2421            }
2422            Expression::CurrentTime(f) => self.generate_current_time(f),
2423            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2424            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2425            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2426            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2427            Expression::DateDiff(f) => self.generate_datediff(f),
2428            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2429            Expression::Extract(f) => self.generate_extract(f),
2430            Expression::ToDate(f) => self.generate_to_date(f),
2431            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2432
2433            // Control flow functions
2434            Expression::Coalesce(f) => {
2435                // Use original function name if preserved (COALESCE, IFNULL)
2436                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2437                self.generate_vararg_func(func_name, &f.expressions)
2438            }
2439            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2440            Expression::IfFunc(f) => self.generate_if_func(f),
2441            Expression::IfNull(f) => self.generate_ifnull(f),
2442            Expression::Nvl(f) => self.generate_nvl(f),
2443            Expression::Nvl2(f) => self.generate_nvl2(f),
2444
2445            // Type conversion
2446            Expression::TryCast(cast) => self.generate_try_cast(cast),
2447            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2448
2449            // Typed aggregate functions
2450            Expression::Count(f) => self.generate_count(f),
2451            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2452            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2453            Expression::Min(f) => self.generate_agg_func("MIN", f),
2454            Expression::Max(f) => self.generate_agg_func("MAX", f),
2455            Expression::GroupConcat(f) => self.generate_group_concat(f),
2456            Expression::StringAgg(f) => self.generate_string_agg(f),
2457            Expression::ListAgg(f) => self.generate_listagg(f),
2458            Expression::ArrayAgg(f) => {
2459                // Allow cross-dialect transforms to override the function name
2460                // (e.g., COLLECT_LIST for Spark)
2461                let override_name = f
2462                    .name
2463                    .as_ref()
2464                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
2465                    .map(|n| n.to_uppercase());
2466                match override_name {
2467                    Some(name) => self.generate_agg_func(&name, f),
2468                    None => self.generate_agg_func("ARRAY_AGG", f),
2469                }
2470            }
2471            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2472            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2473            Expression::SumIf(f) => self.generate_sum_if(f),
2474            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2475            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2476            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2477            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2478            Expression::VarPop(f) => {
2479                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2480                    "VARIANCE_POP"
2481                } else {
2482                    "VAR_POP"
2483                };
2484                self.generate_agg_func(name, f)
2485            }
2486            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2487            Expression::Skewness(f) => {
2488                let name = match self.config.dialect {
2489                    Some(DialectType::Snowflake) => "SKEW",
2490                    _ => "SKEWNESS",
2491                };
2492                self.generate_agg_func(name, f)
2493            }
2494            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2495            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2496            Expression::First(f) => self.generate_agg_func("FIRST", f),
2497            Expression::Last(f) => self.generate_agg_func("LAST", f),
2498            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2499            Expression::ApproxDistinct(f) => {
2500                match self.config.dialect {
2501                    Some(DialectType::Hive)
2502                    | Some(DialectType::Spark)
2503                    | Some(DialectType::Databricks)
2504                    | Some(DialectType::BigQuery) => {
2505                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2506                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2507                    }
2508                    Some(DialectType::Redshift) => {
2509                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2510                        self.write_keyword("APPROXIMATE COUNT");
2511                        self.write("(");
2512                        self.write_keyword("DISTINCT");
2513                        self.write(" ");
2514                        self.generate_expression(&f.this)?;
2515                        self.write(")");
2516                        Ok(())
2517                    }
2518                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2519                }
2520            }
2521            Expression::ApproxCountDistinct(f) => {
2522                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2523            }
2524            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2525            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2526            Expression::LogicalAnd(f) => {
2527                let name = match self.config.dialect {
2528                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2529                    Some(DialectType::Spark)
2530                    | Some(DialectType::Databricks)
2531                    | Some(DialectType::PostgreSQL)
2532                    | Some(DialectType::DuckDB)
2533                    | Some(DialectType::Redshift) => "BOOL_AND",
2534                    Some(DialectType::Oracle)
2535                    | Some(DialectType::SQLite)
2536                    | Some(DialectType::MySQL) => "MIN",
2537                    _ => "BOOL_AND",
2538                };
2539                self.generate_agg_func(name, f)
2540            }
2541            Expression::LogicalOr(f) => {
2542                let name = match self.config.dialect {
2543                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2544                    Some(DialectType::Spark)
2545                    | Some(DialectType::Databricks)
2546                    | Some(DialectType::PostgreSQL)
2547                    | Some(DialectType::DuckDB)
2548                    | Some(DialectType::Redshift) => "BOOL_OR",
2549                    Some(DialectType::Oracle)
2550                    | Some(DialectType::SQLite)
2551                    | Some(DialectType::MySQL) => "MAX",
2552                    _ => "BOOL_OR",
2553                };
2554                self.generate_agg_func(name, f)
2555            }
2556
2557            // Typed window functions
2558            Expression::RowNumber(_) => {
2559                if self.config.dialect == Some(DialectType::ClickHouse) {
2560                    self.write("row_number");
2561                } else {
2562                    self.write_keyword("ROW_NUMBER");
2563                }
2564                self.write("()");
2565                Ok(())
2566            }
2567            Expression::Rank(r) => {
2568                self.write_keyword("RANK");
2569                self.write("(");
2570                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2571                if !r.args.is_empty() {
2572                    for (i, arg) in r.args.iter().enumerate() {
2573                        if i > 0 {
2574                            self.write(", ");
2575                        }
2576                        self.generate_expression(arg)?;
2577                    }
2578                } else if let Some(order_by) = &r.order_by {
2579                    // DuckDB: RANK(ORDER BY col)
2580                    self.write_keyword(" ORDER BY ");
2581                    for (i, ob) in order_by.iter().enumerate() {
2582                        if i > 0 {
2583                            self.write(", ");
2584                        }
2585                        self.generate_ordered(ob)?;
2586                    }
2587                }
2588                self.write(")");
2589                Ok(())
2590            }
2591            Expression::DenseRank(dr) => {
2592                self.write_keyword("DENSE_RANK");
2593                self.write("(");
2594                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2595                for (i, arg) in dr.args.iter().enumerate() {
2596                    if i > 0 {
2597                        self.write(", ");
2598                    }
2599                    self.generate_expression(arg)?;
2600                }
2601                self.write(")");
2602                Ok(())
2603            }
2604            Expression::NTile(f) => self.generate_ntile(f),
2605            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2606            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2607            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
2608            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
2609            Expression::NthValue(f) => self.generate_nth_value(f),
2610            Expression::PercentRank(pr) => {
2611                self.write_keyword("PERCENT_RANK");
2612                self.write("(");
2613                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2614                if !pr.args.is_empty() {
2615                    for (i, arg) in pr.args.iter().enumerate() {
2616                        if i > 0 {
2617                            self.write(", ");
2618                        }
2619                        self.generate_expression(arg)?;
2620                    }
2621                } else if let Some(order_by) = &pr.order_by {
2622                    // DuckDB: PERCENT_RANK(ORDER BY col)
2623                    self.write_keyword(" ORDER BY ");
2624                    for (i, ob) in order_by.iter().enumerate() {
2625                        if i > 0 {
2626                            self.write(", ");
2627                        }
2628                        self.generate_ordered(ob)?;
2629                    }
2630                }
2631                self.write(")");
2632                Ok(())
2633            }
2634            Expression::CumeDist(cd) => {
2635                self.write_keyword("CUME_DIST");
2636                self.write("(");
2637                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2638                if !cd.args.is_empty() {
2639                    for (i, arg) in cd.args.iter().enumerate() {
2640                        if i > 0 {
2641                            self.write(", ");
2642                        }
2643                        self.generate_expression(arg)?;
2644                    }
2645                } else if let Some(order_by) = &cd.order_by {
2646                    // DuckDB: CUME_DIST(ORDER BY col)
2647                    self.write_keyword(" ORDER BY ");
2648                    for (i, ob) in order_by.iter().enumerate() {
2649                        if i > 0 {
2650                            self.write(", ");
2651                        }
2652                        self.generate_ordered(ob)?;
2653                    }
2654                }
2655                self.write(")");
2656                Ok(())
2657            }
2658            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2659            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2660
2661            // Additional string functions
2662            Expression::Contains(f) => {
2663                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2664            }
2665            Expression::StartsWith(f) => {
2666                let name = match self.config.dialect {
2667                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2668                    _ => "STARTS_WITH",
2669                };
2670                self.generate_binary_func(name, &f.this, &f.expression)
2671            }
2672            Expression::EndsWith(f) => {
2673                let name = match self.config.dialect {
2674                    Some(DialectType::Snowflake) => "ENDSWITH",
2675                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2676                    Some(DialectType::ClickHouse) => "endsWith",
2677                    _ => "ENDS_WITH",
2678                };
2679                self.generate_binary_func(name, &f.this, &f.expression)
2680            }
2681            Expression::Position(f) => self.generate_position(f),
2682            Expression::Initcap(f) => match self.config.dialect {
2683                Some(DialectType::Presto)
2684                | Some(DialectType::Trino)
2685                | Some(DialectType::Athena) => {
2686                    self.write_keyword("REGEXP_REPLACE");
2687                    self.write("(");
2688                    self.generate_expression(&f.this)?;
2689                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2690                    Ok(())
2691                }
2692                _ => self.generate_simple_func("INITCAP", &f.this),
2693            },
2694            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2695            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2696            Expression::CharFunc(f) => self.generate_char_func(f),
2697            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2698            Expression::Levenshtein(f) => {
2699                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2700            }
2701
2702            // Additional math functions
2703            Expression::ModFunc(f) => self.generate_mod_func(f),
2704            Expression::Random(_) => {
2705                self.write_keyword("RANDOM");
2706                self.write("()");
2707                Ok(())
2708            }
2709            Expression::Rand(f) => self.generate_rand(f),
2710            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2711            Expression::Pi(_) => {
2712                self.write_keyword("PI");
2713                self.write("()");
2714                Ok(())
2715            }
2716            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2717            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2718            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2719            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2720            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2721            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2722            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2723            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2724            Expression::Atan2(f) => {
2725                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2726                self.generate_binary_func(name, &f.this, &f.expression)
2727            }
2728
2729            // Control flow
2730            Expression::Decode(f) => self.generate_decode(f),
2731
2732            // Additional date/time functions
2733            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2734            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2735            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2736            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2737            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2738            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2739            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2740            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2741            Expression::DayOfWeek(f) => {
2742                let name = match self.config.dialect {
2743                    Some(DialectType::Presto)
2744                    | Some(DialectType::Trino)
2745                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2746                    Some(DialectType::DuckDB) => "ISODOW",
2747                    _ => "DAYOFWEEK",
2748                };
2749                self.generate_simple_func(name, &f.this)
2750            }
2751            Expression::DayOfMonth(f) => {
2752                let name = match self.config.dialect {
2753                    Some(DialectType::Presto)
2754                    | Some(DialectType::Trino)
2755                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2756                    _ => "DAYOFMONTH",
2757                };
2758                self.generate_simple_func(name, &f.this)
2759            }
2760            Expression::DayOfYear(f) => {
2761                let name = match self.config.dialect {
2762                    Some(DialectType::Presto)
2763                    | Some(DialectType::Trino)
2764                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2765                    _ => "DAYOFYEAR",
2766                };
2767                self.generate_simple_func(name, &f.this)
2768            }
2769            Expression::WeekOfYear(f) => {
2770                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2771                let name = match self.config.dialect {
2772                    Some(DialectType::Hive)
2773                    | Some(DialectType::DuckDB)
2774                    | Some(DialectType::Spark)
2775                    | Some(DialectType::Databricks)
2776                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2777                    _ => "WEEK_OF_YEAR",
2778                };
2779                self.generate_simple_func(name, &f.this)
2780            }
2781            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2782            Expression::AddMonths(f) => {
2783                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2784            }
2785            Expression::MonthsBetween(f) => {
2786                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2787            }
2788            Expression::LastDay(f) => self.generate_last_day(f),
2789            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2790            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2791            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2792            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2793            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2794            Expression::MakeDate(f) => self.generate_make_date(f),
2795            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2796            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2797
2798            // Array functions
2799            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2800            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2801            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2802            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2803            Expression::ArrayContains(f) => {
2804                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2805            }
2806            Expression::ArrayPosition(f) => {
2807                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2808            }
2809            Expression::ArrayAppend(f) => {
2810                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2811            }
2812            Expression::ArrayPrepend(f) => {
2813                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2814            }
2815            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2816            Expression::ArraySort(f) => self.generate_array_sort(f),
2817            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2818            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2819            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2820            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2821            Expression::Unnest(f) => self.generate_unnest(f),
2822            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2823            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2824            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2825            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2826            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2827            Expression::ArrayCompact(f) => {
2828                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2829                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2830                    self.write("LIST_FILTER(");
2831                    self.generate_expression(&f.this)?;
2832                    self.write(", _u -> NOT _u IS NULL)");
2833                    Ok(())
2834                } else {
2835                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2836                }
2837            }
2838            Expression::ArrayIntersect(f) => {
2839                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2840                self.generate_vararg_func(func_name, &f.expressions)
2841            }
2842            Expression::ArrayUnion(f) => {
2843                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2844            }
2845            Expression::ArrayExcept(f) => {
2846                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2847            }
2848            Expression::ArrayRemove(f) => {
2849                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2850            }
2851            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2852            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2853            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2854
2855            // Struct functions
2856            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2857            Expression::StructExtract(f) => self.generate_struct_extract(f),
2858            Expression::NamedStruct(f) => self.generate_named_struct(f),
2859
2860            // Map functions
2861            Expression::MapFunc(f) => self.generate_map_constructor(f),
2862            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2863            Expression::MapFromArrays(f) => {
2864                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2865            }
2866            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2867            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2868            Expression::MapContainsKey(f) => {
2869                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2870            }
2871            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2872            Expression::ElementAt(f) => {
2873                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2874            }
2875            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2876            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2877
2878            // JSON functions
2879            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2880            Expression::JsonExtractScalar(f) => {
2881                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2882            }
2883            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2884            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2885            Expression::JsonObject(f) => self.generate_json_object(f),
2886            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2887            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2888            Expression::JsonArrayLength(f) => {
2889                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2890            }
2891            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2892            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2893            Expression::ParseJson(f) => {
2894                let name = match self.config.dialect {
2895                    Some(DialectType::Presto)
2896                    | Some(DialectType::Trino)
2897                    | Some(DialectType::Athena) => "JSON_PARSE",
2898                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2899                        // PostgreSQL: CAST(x AS JSON)
2900                        self.write_keyword("CAST");
2901                        self.write("(");
2902                        self.generate_expression(&f.this)?;
2903                        self.write_keyword(" AS ");
2904                        self.write_keyword("JSON");
2905                        self.write(")");
2906                        return Ok(());
2907                    }
2908                    Some(DialectType::Hive)
2909                    | Some(DialectType::Spark)
2910                    | Some(DialectType::MySQL)
2911                    | Some(DialectType::SingleStore)
2912                    | Some(DialectType::TiDB)
2913                    | Some(DialectType::TSQL) => {
2914                        // Hive/Spark/MySQL/TSQL: just emit the string literal
2915                        self.generate_expression(&f.this)?;
2916                        return Ok(());
2917                    }
2918                    Some(DialectType::DuckDB) => "JSON",
2919                    _ => "PARSE_JSON",
2920                };
2921                self.generate_simple_func(name, &f.this)
2922            }
2923            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
2924            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
2925            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
2926            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
2927            Expression::JsonMergePatch(f) => {
2928                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
2929            }
2930            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
2931            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
2932
2933            // Type casting/conversion
2934            Expression::Convert(f) => self.generate_convert(f),
2935            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
2936
2937            // Additional expressions
2938            Expression::Lambda(f) => self.generate_lambda(f),
2939            Expression::Parameter(f) => self.generate_parameter(f),
2940            Expression::Placeholder(f) => self.generate_placeholder(f),
2941            Expression::NamedArgument(f) => self.generate_named_argument(f),
2942            Expression::TableArgument(f) => self.generate_table_argument(f),
2943            Expression::SqlComment(f) => self.generate_sql_comment(f),
2944
2945            // Additional predicates
2946            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
2947            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
2948            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
2949            Expression::SimilarTo(f) => self.generate_similar_to(f),
2950            Expression::Any(f) => self.generate_quantified("ANY", f),
2951            Expression::All(f) => self.generate_quantified("ALL", f),
2952            Expression::Overlaps(f) => self.generate_overlaps(f),
2953
2954            // Bitwise operations
2955            Expression::BitwiseLeftShift(op) => {
2956                if matches!(
2957                    self.config.dialect,
2958                    Some(DialectType::Presto) | Some(DialectType::Trino)
2959                ) {
2960                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
2961                    self.write("(");
2962                    self.generate_expression(&op.left)?;
2963                    self.write(", ");
2964                    self.generate_expression(&op.right)?;
2965                    self.write(")");
2966                    Ok(())
2967                } else if matches!(
2968                    self.config.dialect,
2969                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2970                ) {
2971                    self.write_keyword("SHIFTLEFT");
2972                    self.write("(");
2973                    self.generate_expression(&op.left)?;
2974                    self.write(", ");
2975                    self.generate_expression(&op.right)?;
2976                    self.write(")");
2977                    Ok(())
2978                } else {
2979                    self.generate_binary_op(op, "<<")
2980                }
2981            }
2982            Expression::BitwiseRightShift(op) => {
2983                if matches!(
2984                    self.config.dialect,
2985                    Some(DialectType::Presto) | Some(DialectType::Trino)
2986                ) {
2987                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
2988                    self.write("(");
2989                    self.generate_expression(&op.left)?;
2990                    self.write(", ");
2991                    self.generate_expression(&op.right)?;
2992                    self.write(")");
2993                    Ok(())
2994                } else if matches!(
2995                    self.config.dialect,
2996                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2997                ) {
2998                    self.write_keyword("SHIFTRIGHT");
2999                    self.write("(");
3000                    self.generate_expression(&op.left)?;
3001                    self.write(", ");
3002                    self.generate_expression(&op.right)?;
3003                    self.write(")");
3004                    Ok(())
3005                } else {
3006                    self.generate_binary_op(op, ">>")
3007                }
3008            }
3009            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3010            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3011            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3012
3013            // Array/struct/map access
3014            Expression::Subscript(s) => self.generate_subscript(s),
3015            Expression::Dot(d) => self.generate_dot_access(d),
3016            Expression::MethodCall(m) => self.generate_method_call(m),
3017            Expression::ArraySlice(s) => self.generate_array_slice(s),
3018
3019            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3020            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3021            Expression::Add(op) => self.generate_binary_op(op, "+"),
3022            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3023            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3024            Expression::Div(op) => self.generate_binary_op(op, "/"),
3025            Expression::IntDiv(f) => {
3026                use crate::dialects::DialectType;
3027                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3028                    // DuckDB uses // operator for integer division
3029                    self.generate_expression(&f.this)?;
3030                    self.write(" // ");
3031                    self.generate_expression(&f.expression)?;
3032                    Ok(())
3033                } else if matches!(
3034                    self.config.dialect,
3035                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3036                ) {
3037                    // Hive/Spark use DIV as an infix operator
3038                    self.generate_expression(&f.this)?;
3039                    self.write(" ");
3040                    self.write_keyword("DIV");
3041                    self.write(" ");
3042                    self.generate_expression(&f.expression)?;
3043                    Ok(())
3044                } else {
3045                    // Other dialects use DIV function
3046                    self.write_keyword("DIV");
3047                    self.write("(");
3048                    self.generate_expression(&f.this)?;
3049                    self.write(", ");
3050                    self.generate_expression(&f.expression)?;
3051                    self.write(")");
3052                    Ok(())
3053                }
3054            }
3055            Expression::Mod(op) => {
3056                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3057                    self.generate_binary_op(op, "MOD")
3058                } else {
3059                    self.generate_binary_op(op, "%")
3060                }
3061            }
3062            Expression::Eq(op) => self.generate_binary_op(op, "="),
3063            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3064            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3065            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3066            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3067            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3068            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3069            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3070            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3071            Expression::Concat(op) => {
3072                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3073                if self.config.dialect == Some(DialectType::Solr) {
3074                    self.generate_binary_op(op, "OR")
3075                } else if self.config.dialect == Some(DialectType::MySQL) {
3076                    self.generate_mysql_concat_from_concat(op)
3077                } else {
3078                    self.generate_binary_op(op, "||")
3079                }
3080            }
3081            Expression::BitwiseAnd(op) => {
3082                // Presto/Trino use BITWISE_AND function
3083                if matches!(
3084                    self.config.dialect,
3085                    Some(DialectType::Presto) | Some(DialectType::Trino)
3086                ) {
3087                    self.write_keyword("BITWISE_AND");
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::BitwiseOr(op) => {
3099                // Presto/Trino use BITWISE_OR function
3100                if matches!(
3101                    self.config.dialect,
3102                    Some(DialectType::Presto) | Some(DialectType::Trino)
3103                ) {
3104                    self.write_keyword("BITWISE_OR");
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 {
3112                    self.generate_binary_op(op, "|")
3113                }
3114            }
3115            Expression::BitwiseXor(op) => {
3116                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3117                if matches!(
3118                    self.config.dialect,
3119                    Some(DialectType::Presto) | Some(DialectType::Trino)
3120                ) {
3121                    self.write_keyword("BITWISE_XOR");
3122                    self.write("(");
3123                    self.generate_expression(&op.left)?;
3124                    self.write(", ");
3125                    self.generate_expression(&op.right)?;
3126                    self.write(")");
3127                    Ok(())
3128                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3129                    self.generate_binary_op(op, "#")
3130                } else {
3131                    self.generate_binary_op(op, "^")
3132                }
3133            }
3134            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3135            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3136            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3137            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3138            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3139            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3140            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3141            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3142            Expression::JSONBContains(f) => {
3143                // PostgreSQL JSONB contains key operator: a ? b
3144                self.generate_expression(&f.this)?;
3145                self.write_space();
3146                self.write("?");
3147                self.write_space();
3148                self.generate_expression(&f.expression)
3149            }
3150            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3151            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3152            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3153            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3154            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3155            Expression::BitwiseNot(op) => {
3156                // Presto/Trino use BITWISE_NOT function
3157                if matches!(
3158                    self.config.dialect,
3159                    Some(DialectType::Presto) | Some(DialectType::Trino)
3160                ) {
3161                    self.write_keyword("BITWISE_NOT");
3162                    self.write("(");
3163                    self.generate_expression(&op.this)?;
3164                    self.write(")");
3165                    Ok(())
3166                } else {
3167                    self.generate_unary_op(op, "~")
3168                }
3169            }
3170            Expression::In(in_expr) => self.generate_in(in_expr),
3171            Expression::Between(between) => self.generate_between(between),
3172            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3173            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3174            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3175            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3176            Expression::Is(is_expr) => self.generate_is(is_expr),
3177            Expression::Exists(exists) => self.generate_exists(exists),
3178            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3179            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3180            Expression::Paren(paren) => {
3181                // JoinedTable already outputs its own parentheses, so don't double-wrap
3182                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3183
3184                if !skip_parens {
3185                    self.write("(");
3186                    if self.config.pretty {
3187                        self.write_newline();
3188                        self.indent_level += 1;
3189                        self.write_indent();
3190                    }
3191                }
3192                self.generate_expression(&paren.this)?;
3193                if !skip_parens {
3194                    if self.config.pretty {
3195                        self.write_newline();
3196                        self.indent_level -= 1;
3197                        self.write_indent();
3198                    }
3199                    self.write(")");
3200                }
3201                // Output trailing comments after closing paren
3202                for comment in &paren.trailing_comments {
3203                    self.write(" ");
3204                    self.write_formatted_comment(comment);
3205                }
3206                Ok(())
3207            }
3208            Expression::Array(arr) => self.generate_array(arr),
3209            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3210            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3211            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3212            Expression::DataType(dt) => self.generate_data_type(dt),
3213            Expression::Raw(raw) => {
3214                self.write(&raw.sql);
3215                Ok(())
3216            }
3217            Expression::Command(cmd) => {
3218                self.write(&cmd.this);
3219                Ok(())
3220            }
3221            Expression::Kill(kill) => {
3222                self.write_keyword("KILL");
3223                if let Some(kind) = &kill.kind {
3224                    self.write_space();
3225                    self.write_keyword(kind);
3226                }
3227                self.write_space();
3228                self.generate_expression(&kill.this)?;
3229                Ok(())
3230            }
3231            Expression::Execute(exec) => {
3232                self.write_keyword("EXEC");
3233                self.write_space();
3234                self.generate_expression(&exec.this)?;
3235                for (i, param) in exec.parameters.iter().enumerate() {
3236                    if i == 0 {
3237                        self.write_space();
3238                    } else {
3239                        self.write(", ");
3240                    }
3241                    self.write(&param.name);
3242                    self.write("=");
3243                    self.generate_expression(&param.value)?;
3244                }
3245                Ok(())
3246            }
3247            Expression::Annotated(annotated) => {
3248                self.generate_expression(&annotated.this)?;
3249                for comment in &annotated.trailing_comments {
3250                    self.write(" ");
3251                    self.write_formatted_comment(comment);
3252                }
3253                Ok(())
3254            }
3255
3256            // DDL statements
3257            Expression::CreateTable(ct) => self.generate_create_table(ct),
3258            Expression::DropTable(dt) => self.generate_drop_table(dt),
3259            Expression::AlterTable(at) => self.generate_alter_table(at),
3260            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3261            Expression::DropIndex(di) => self.generate_drop_index(di),
3262            Expression::CreateView(cv) => self.generate_create_view(cv),
3263            Expression::DropView(dv) => self.generate_drop_view(dv),
3264            Expression::AlterView(av) => self.generate_alter_view(av),
3265            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3266            Expression::Truncate(tr) => self.generate_truncate(tr),
3267            Expression::Use(u) => self.generate_use(u),
3268            // Phase 4: Additional DDL statements
3269            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3270            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3271            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3272            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3273            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3274            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3275            Expression::DropFunction(df) => self.generate_drop_function(df),
3276            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3277            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3278            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3279            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3280            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3281            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3282            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3283            Expression::CreateType(ct) => self.generate_create_type(ct),
3284            Expression::DropType(dt) => self.generate_drop_type(dt),
3285            Expression::Describe(d) => self.generate_describe(d),
3286            Expression::Show(s) => self.generate_show(s),
3287
3288            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3289            Expression::Cache(c) => self.generate_cache(c),
3290            Expression::Uncache(u) => self.generate_uncache(u),
3291            Expression::LoadData(l) => self.generate_load_data(l),
3292            Expression::Pragma(p) => self.generate_pragma(p),
3293            Expression::Grant(g) => self.generate_grant(g),
3294            Expression::Revoke(r) => self.generate_revoke(r),
3295            Expression::Comment(c) => self.generate_comment(c),
3296            Expression::SetStatement(s) => self.generate_set_statement(s),
3297
3298            // PIVOT/UNPIVOT
3299            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3300            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3301
3302            // VALUES table constructor
3303            Expression::Values(values) => self.generate_values(values),
3304
3305            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3306            Expression::AIAgg(e) => self.generate_ai_agg(e),
3307            Expression::AIClassify(e) => self.generate_ai_classify(e),
3308            Expression::AddPartition(e) => self.generate_add_partition(e),
3309            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3310            Expression::Aliases(e) => self.generate_aliases(e),
3311            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3312            Expression::AlterColumn(e) => self.generate_alter_column(e),
3313            Expression::AlterSession(e) => self.generate_alter_session(e),
3314            Expression::AlterSet(e) => self.generate_alter_set(e),
3315            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3316            Expression::Analyze(e) => self.generate_analyze(e),
3317            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3318            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3319            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3320            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3321            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3322            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3323            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3324            Expression::Anonymous(e) => self.generate_anonymous(e),
3325            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3326            Expression::Apply(e) => self.generate_apply(e),
3327            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3328            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3329            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3330            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3331            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3332            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3333            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3334            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3335            Expression::ArgMax(e) => self.generate_arg_max(e),
3336            Expression::ArgMin(e) => self.generate_arg_min(e),
3337            Expression::ArrayAll(e) => self.generate_array_all(e),
3338            Expression::ArrayAny(e) => self.generate_array_any(e),
3339            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3340            Expression::ArraySum(e) => self.generate_array_sum(e),
3341            Expression::AtIndex(e) => self.generate_at_index(e),
3342            Expression::Attach(e) => self.generate_attach(e),
3343            Expression::AttachOption(e) => self.generate_attach_option(e),
3344            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3345            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3346            Expression::BackupProperty(e) => self.generate_backup_property(e),
3347            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3348            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3349            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3350            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3351            Expression::Booland(e) => self.generate_booland(e),
3352            Expression::Boolor(e) => self.generate_boolor(e),
3353            Expression::BuildProperty(e) => self.generate_build_property(e),
3354            Expression::ByteString(e) => self.generate_byte_string(e),
3355            Expression::CaseSpecificColumnConstraint(e) => {
3356                self.generate_case_specific_column_constraint(e)
3357            }
3358            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3359            Expression::Changes(e) => self.generate_changes(e),
3360            Expression::CharacterSetColumnConstraint(e) => {
3361                self.generate_character_set_column_constraint(e)
3362            }
3363            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3364            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3365            Expression::CheckJson(e) => self.generate_check_json(e),
3366            Expression::CheckXml(e) => self.generate_check_xml(e),
3367            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3368            Expression::Clone(e) => self.generate_clone(e),
3369            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3370            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3371            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3372            Expression::CollateProperty(e) => self.generate_collate_property(e),
3373            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3374            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3375            Expression::ColumnPosition(e) => self.generate_column_position(e),
3376            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3377            Expression::Columns(e) => self.generate_columns(e),
3378            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3379            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3380            Expression::Commit(e) => self.generate_commit(e),
3381            Expression::Comprehension(e) => self.generate_comprehension(e),
3382            Expression::Compress(e) => self.generate_compress(e),
3383            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3384            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3385            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3386            Expression::Constraint(e) => self.generate_constraint(e),
3387            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3388            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3389            Expression::Copy(e) => self.generate_copy(e),
3390            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3391            Expression::Corr(e) => self.generate_corr(e),
3392            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3393            Expression::CovarPop(e) => self.generate_covar_pop(e),
3394            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3395            Expression::Credentials(e) => self.generate_credentials(e),
3396            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3397            Expression::Cte(e) => self.generate_cte(e),
3398            Expression::Cube(e) => self.generate_cube(e),
3399            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3400            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3401            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3402            Expression::CurrentUser(e) => self.generate_current_user(e),
3403            Expression::DPipe(e) => self.generate_d_pipe(e),
3404            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3405            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3406            Expression::Date(e) => self.generate_date_func(e),
3407            Expression::DateBin(e) => self.generate_date_bin(e),
3408            Expression::DateFormatColumnConstraint(e) => {
3409                self.generate_date_format_column_constraint(e)
3410            }
3411            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3412            Expression::Datetime(e) => self.generate_datetime(e),
3413            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3414            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3415            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3416            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3417            Expression::Dayname(e) => self.generate_dayname(e),
3418            Expression::Declare(e) => self.generate_declare(e),
3419            Expression::DeclareItem(e) => self.generate_declare_item(e),
3420            Expression::DecodeCase(e) => self.generate_decode_case(e),
3421            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3422            Expression::DecompressString(e) => self.generate_decompress_string(e),
3423            Expression::Decrypt(e) => self.generate_decrypt(e),
3424            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3425            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3426            Expression::Detach(e) => self.generate_detach(e),
3427            Expression::DictProperty(e) => self.generate_dict_property(e),
3428            Expression::DictRange(e) => self.generate_dict_range(e),
3429            Expression::Directory(e) => self.generate_directory(e),
3430            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3431            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3432            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3433            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3434            Expression::DotProduct(e) => self.generate_dot_product(e),
3435            Expression::DropPartition(e) => self.generate_drop_partition(e),
3436            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3437            Expression::Elt(e) => self.generate_elt(e),
3438            Expression::Encode(e) => self.generate_encode(e),
3439            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3440            Expression::Encrypt(e) => self.generate_encrypt(e),
3441            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3442            Expression::EngineProperty(e) => self.generate_engine_property(e),
3443            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3444            Expression::EphemeralColumnConstraint(e) => {
3445                self.generate_ephemeral_column_constraint(e)
3446            }
3447            Expression::EqualNull(e) => self.generate_equal_null(e),
3448            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3449            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3450            Expression::Export(e) => self.generate_export(e),
3451            Expression::ExternalProperty(e) => self.generate_external_property(e),
3452            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3453            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3454            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3455            Expression::Fetch(e) => self.generate_fetch(e),
3456            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3457            Expression::Filter(e) => self.generate_filter(e),
3458            Expression::Float64(e) => self.generate_float64(e),
3459            Expression::ForIn(e) => self.generate_for_in(e),
3460            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3461            Expression::Format(e) => self.generate_format(e),
3462            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3463            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3464            Expression::From(e) => self.generate_from(e),
3465            Expression::FromBase(e) => self.generate_from_base(e),
3466            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3467            Expression::GapFill(e) => self.generate_gap_fill(e),
3468            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3469            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3470            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3471            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3472            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3473                self.generate_generated_as_identity_column_constraint(e)
3474            }
3475            Expression::GeneratedAsRowColumnConstraint(e) => {
3476                self.generate_generated_as_row_column_constraint(e)
3477            }
3478            Expression::Get(e) => self.generate_get(e),
3479            Expression::GetExtract(e) => self.generate_get_extract(e),
3480            Expression::Getbit(e) => self.generate_getbit(e),
3481            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3482            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3483            Expression::Group(e) => self.generate_group(e),
3484            Expression::GroupBy(e) => self.generate_group_by(e),
3485            Expression::Grouping(e) => self.generate_grouping(e),
3486            Expression::GroupingId(e) => self.generate_grouping_id(e),
3487            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3488            Expression::HashAgg(e) => self.generate_hash_agg(e),
3489            Expression::Having(e) => self.generate_having(e),
3490            Expression::HavingMax(e) => self.generate_having_max(e),
3491            Expression::Heredoc(e) => self.generate_heredoc(e),
3492            Expression::HexEncode(e) => self.generate_hex_encode(e),
3493            Expression::Hll(e) => self.generate_hll(e),
3494            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3495            Expression::IncludeProperty(e) => self.generate_include_property(e),
3496            Expression::Index(e) => self.generate_index(e),
3497            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3498            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3499            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3500            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3501            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3502            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3503            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3504            Expression::Install(e) => self.generate_install(e),
3505            Expression::IntervalOp(e) => self.generate_interval_op(e),
3506            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3507            Expression::IntoClause(e) => self.generate_into_clause(e),
3508            Expression::Introducer(e) => self.generate_introducer(e),
3509            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3510            Expression::JSON(e) => self.generate_json(e),
3511            Expression::JSONArray(e) => self.generate_json_array(e),
3512            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3513            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3514            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3515            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3516            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3517            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3518            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3519            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3520            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3521            Expression::JSONExists(e) => self.generate_json_exists(e),
3522            Expression::JSONCast(e) => self.generate_json_cast(e),
3523            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3524            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3525            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3526            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3527            Expression::JSONFormat(e) => self.generate_json_format(e),
3528            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3529            Expression::JSONKeys(e) => self.generate_json_keys(e),
3530            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3531            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3532            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3533            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3534            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3535            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3536            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3537            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3538            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3539            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3540            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3541            Expression::JSONRemove(e) => self.generate_json_remove(e),
3542            Expression::JSONSchema(e) => self.generate_json_schema(e),
3543            Expression::JSONSet(e) => self.generate_json_set(e),
3544            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3545            Expression::JSONTable(e) => self.generate_json_table(e),
3546            Expression::JSONType(e) => self.generate_json_type(e),
3547            Expression::JSONValue(e) => self.generate_json_value(e),
3548            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3549            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3550            Expression::JoinHint(e) => self.generate_join_hint(e),
3551            Expression::JournalProperty(e) => self.generate_journal_property(e),
3552            Expression::LanguageProperty(e) => self.generate_language_property(e),
3553            Expression::Lateral(e) => self.generate_lateral(e),
3554            Expression::LikeProperty(e) => self.generate_like_property(e),
3555            Expression::Limit(e) => self.generate_limit(e),
3556            Expression::LimitOptions(e) => self.generate_limit_options(e),
3557            Expression::List(e) => self.generate_list(e),
3558            Expression::ToMap(e) => self.generate_tomap(e),
3559            Expression::Localtime(e) => self.generate_localtime(e),
3560            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3561            Expression::LocationProperty(e) => self.generate_location_property(e),
3562            Expression::Lock(e) => self.generate_lock(e),
3563            Expression::LockProperty(e) => self.generate_lock_property(e),
3564            Expression::LockingProperty(e) => self.generate_locking_property(e),
3565            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3566            Expression::LogProperty(e) => self.generate_log_property(e),
3567            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3568            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3569            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3570            Expression::MakeInterval(e) => self.generate_make_interval(e),
3571            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3572            Expression::Map(e) => self.generate_map(e),
3573            Expression::MapCat(e) => self.generate_map_cat(e),
3574            Expression::MapDelete(e) => self.generate_map_delete(e),
3575            Expression::MapInsert(e) => self.generate_map_insert(e),
3576            Expression::MapPick(e) => self.generate_map_pick(e),
3577            Expression::MaskingPolicyColumnConstraint(e) => {
3578                self.generate_masking_policy_column_constraint(e)
3579            }
3580            Expression::MatchAgainst(e) => self.generate_match_against(e),
3581            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3582            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3583            Expression::Merge(e) => self.generate_merge(e),
3584            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3585            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3586            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3587            Expression::Minhash(e) => self.generate_minhash(e),
3588            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3589            Expression::Monthname(e) => self.generate_monthname(e),
3590            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3591            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3592            Expression::Normal(e) => self.generate_normal(e),
3593            Expression::Normalize(e) => self.generate_normalize(e),
3594            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3595            Expression::Nullif(e) => self.generate_nullif(e),
3596            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3597            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3598            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3599            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3600            Expression::Offset(e) => self.generate_offset(e),
3601            Expression::Qualify(e) => self.generate_qualify(e),
3602            Expression::OnCluster(e) => self.generate_on_cluster(e),
3603            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3604            Expression::OnCondition(e) => self.generate_on_condition(e),
3605            Expression::OnConflict(e) => self.generate_on_conflict(e),
3606            Expression::OnProperty(e) => self.generate_on_property(e),
3607            Expression::Opclass(e) => self.generate_opclass(e),
3608            Expression::OpenJSON(e) => self.generate_open_json(e),
3609            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3610            Expression::Operator(e) => self.generate_operator(e),
3611            Expression::OrderBy(e) => self.generate_order_by(e),
3612            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3613            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3614            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3615            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3616            Expression::ParseIp(e) => self.generate_parse_ip(e),
3617            Expression::ParseJSON(e) => self.generate_parse_json(e),
3618            Expression::ParseTime(e) => self.generate_parse_time(e),
3619            Expression::ParseUrl(e) => self.generate_parse_url(e),
3620            Expression::Partition(e) => self.generate_partition_expr(e),
3621            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3622            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3623            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3624            Expression::PartitionByRangePropertyDynamic(e) => {
3625                self.generate_partition_by_range_property_dynamic(e)
3626            }
3627            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3628            Expression::PartitionList(e) => self.generate_partition_list(e),
3629            Expression::PartitionRange(e) => self.generate_partition_range(e),
3630            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3631            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3632            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3633            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3634            Expression::PeriodForSystemTimeConstraint(e) => {
3635                self.generate_period_for_system_time_constraint(e)
3636            }
3637            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3638            Expression::PivotAny(e) => self.generate_pivot_any(e),
3639            Expression::Predict(e) => self.generate_predict(e),
3640            Expression::PreviousDay(e) => self.generate_previous_day(e),
3641            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3642            Expression::PrimaryKeyColumnConstraint(e) => {
3643                self.generate_primary_key_column_constraint(e)
3644            }
3645            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3646            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3647            Expression::OptionsProperty(e) => self.generate_options_property(e),
3648            Expression::Properties(e) => self.generate_properties(e),
3649            Expression::Property(e) => self.generate_property(e),
3650            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3651            Expression::Put(e) => self.generate_put(e),
3652            Expression::Quantile(e) => self.generate_quantile(e),
3653            Expression::QueryBand(e) => self.generate_query_band(e),
3654            Expression::QueryOption(e) => self.generate_query_option(e),
3655            Expression::QueryTransform(e) => self.generate_query_transform(e),
3656            Expression::Randn(e) => self.generate_randn(e),
3657            Expression::Randstr(e) => self.generate_randstr(e),
3658            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3659            Expression::RangeN(e) => self.generate_range_n(e),
3660            Expression::ReadCSV(e) => self.generate_read_csv(e),
3661            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3662            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3663            Expression::Reduce(e) => self.generate_reduce(e),
3664            Expression::Reference(e) => self.generate_reference(e),
3665            Expression::Refresh(e) => self.generate_refresh(e),
3666            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3667            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3668            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3669            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3670            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3671            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3672            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3673            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3674            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3675            Expression::RegrCount(e) => self.generate_regr_count(e),
3676            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3677            Expression::RegrR2(e) => self.generate_regr_r2(e),
3678            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3679            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3680            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3681            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3682            Expression::RegrValx(e) => self.generate_regr_valx(e),
3683            Expression::RegrValy(e) => self.generate_regr_valy(e),
3684            Expression::RemoteWithConnectionModelProperty(e) => {
3685                self.generate_remote_with_connection_model_property(e)
3686            }
3687            Expression::RenameColumn(e) => self.generate_rename_column(e),
3688            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3689            Expression::Returning(e) => self.generate_returning(e),
3690            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3691            Expression::Rollback(e) => self.generate_rollback(e),
3692            Expression::Rollup(e) => self.generate_rollup(e),
3693            Expression::RowFormatDelimitedProperty(e) => {
3694                self.generate_row_format_delimited_property(e)
3695            }
3696            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3697            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3698            Expression::SHA2(e) => self.generate_sha2(e),
3699            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3700            Expression::SafeAdd(e) => self.generate_safe_add(e),
3701            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3702            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3703            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3704            Expression::SampleProperty(e) => self.generate_sample_property(e),
3705            Expression::Schema(e) => self.generate_schema(e),
3706            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3707            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3708            Expression::Search(e) => self.generate_search(e),
3709            Expression::SearchIp(e) => self.generate_search_ip(e),
3710            Expression::SecurityProperty(e) => self.generate_security_property(e),
3711            Expression::SemanticView(e) => self.generate_semantic_view(e),
3712            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3713            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3714            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3715            Expression::Set(e) => self.generate_set(e),
3716            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3717            Expression::SetItem(e) => self.generate_set_item(e),
3718            Expression::SetOperation(e) => self.generate_set_operation(e),
3719            Expression::SetProperty(e) => self.generate_set_property(e),
3720            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3721            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3722            Expression::Slice(e) => self.generate_slice(e),
3723            Expression::SortArray(e) => self.generate_sort_array(e),
3724            Expression::SortBy(e) => self.generate_sort_by(e),
3725            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3726            Expression::SplitPart(e) => self.generate_split_part(e),
3727            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3728            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3729            Expression::StDistance(e) => self.generate_st_distance(e),
3730            Expression::StPoint(e) => self.generate_st_point(e),
3731            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3732            Expression::StandardHash(e) => self.generate_standard_hash(e),
3733            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3734            Expression::StrPosition(e) => self.generate_str_position(e),
3735            Expression::StrToDate(e) => self.generate_str_to_date(e),
3736            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3737            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3738            Expression::StrToMap(e) => self.generate_str_to_map(e),
3739            Expression::StrToTime(e) => self.generate_str_to_time(e),
3740            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3741            Expression::StringToArray(e) => self.generate_string_to_array(e),
3742            Expression::Struct(e) => self.generate_struct(e),
3743            Expression::Stuff(e) => self.generate_stuff(e),
3744            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3745            Expression::Summarize(e) => self.generate_summarize(e),
3746            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3747            Expression::TableAlias(e) => self.generate_table_alias(e),
3748            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3749            Expression::RowsFrom(e) => self.generate_rows_from(e),
3750            Expression::TableSample(e) => self.generate_table_sample(e),
3751            Expression::Tag(e) => self.generate_tag(e),
3752            Expression::Tags(e) => self.generate_tags(e),
3753            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3754            Expression::Time(e) => self.generate_time_func(e),
3755            Expression::TimeAdd(e) => self.generate_time_add(e),
3756            Expression::TimeDiff(e) => self.generate_time_diff(e),
3757            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3758            Expression::TimeSlice(e) => self.generate_time_slice(e),
3759            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3760            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3761            Expression::TimeSub(e) => self.generate_time_sub(e),
3762            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3763            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3764            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3765            Expression::TimeUnit(e) => self.generate_time_unit(e),
3766            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3767            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3768            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3769            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3770            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3771            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3772            Expression::ToBinary(e) => self.generate_to_binary(e),
3773            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3774            Expression::ToChar(e) => self.generate_to_char(e),
3775            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3776            Expression::ToDouble(e) => self.generate_to_double(e),
3777            Expression::ToFile(e) => self.generate_to_file(e),
3778            Expression::ToNumber(e) => self.generate_to_number(e),
3779            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3780            Expression::Transaction(e) => self.generate_transaction(e),
3781            Expression::Transform(e) => self.generate_transform(e),
3782            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3783            Expression::TransientProperty(e) => self.generate_transient_property(e),
3784            Expression::Translate(e) => self.generate_translate(e),
3785            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3786            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3787            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3788            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3789            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3790            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3791            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3792            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3793            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3794            Expression::Unhex(e) => self.generate_unhex(e),
3795            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3796            Expression::Uniform(e) => self.generate_uniform(e),
3797            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3798            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3799            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3800            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3801            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3802            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3803            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3804            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3805            Expression::UtcTime(e) => self.generate_utc_time(e),
3806            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3807            Expression::Uuid(e) => self.generate_uuid(e),
3808            Expression::Var(v) => {
3809                if matches!(self.config.dialect, Some(DialectType::MySQL))
3810                    && v.this.len() > 2
3811                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3812                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3813                {
3814                    return self.generate_identifier(&Identifier {
3815                        name: v.this.clone(),
3816                        quoted: true,
3817                        trailing_comments: Vec::new(),
3818                        span: None,
3819                    });
3820                }
3821                self.write(&v.this);
3822                Ok(())
3823            }
3824            Expression::Variadic(e) => {
3825                self.write_keyword("VARIADIC");
3826                self.write_space();
3827                self.generate_expression(&e.this)?;
3828                Ok(())
3829            }
3830            Expression::VarMap(e) => self.generate_var_map(e),
3831            Expression::VectorSearch(e) => self.generate_vector_search(e),
3832            Expression::Version(e) => self.generate_version(e),
3833            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3834            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3835            Expression::WatermarkColumnConstraint(e) => {
3836                self.generate_watermark_column_constraint(e)
3837            }
3838            Expression::Week(e) => self.generate_week(e),
3839            Expression::When(e) => self.generate_when(e),
3840            Expression::Whens(e) => self.generate_whens(e),
3841            Expression::Where(e) => self.generate_where(e),
3842            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3843            Expression::Window(e) => self.generate_window(e),
3844            Expression::WindowSpec(e) => self.generate_window_spec(e),
3845            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3846            Expression::WithFill(e) => self.generate_with_fill(e),
3847            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3848            Expression::WithOperator(e) => self.generate_with_operator(e),
3849            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3850            Expression::WithSchemaBindingProperty(e) => {
3851                self.generate_with_schema_binding_property(e)
3852            }
3853            Expression::WithSystemVersioningProperty(e) => {
3854                self.generate_with_system_versioning_property(e)
3855            }
3856            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3857            Expression::XMLElement(e) => self.generate_xml_element(e),
3858            Expression::XMLGet(e) => self.generate_xml_get(e),
3859            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3860            Expression::XMLTable(e) => self.generate_xml_table(e),
3861            Expression::Xor(e) => self.generate_xor(e),
3862            Expression::Zipf(e) => self.generate_zipf(e),
3863            _ => {
3864                // Fallback for unimplemented expressions
3865                self.write(&format!("/* unimplemented: {:?} */", expr));
3866                Ok(())
3867            }
3868        }
3869    }
3870
3871    fn generate_select(&mut self, select: &Select) -> Result<()> {
3872        use crate::dialects::DialectType;
3873
3874        // Output leading comments before SELECT
3875        for comment in &select.leading_comments {
3876            self.write_formatted_comment(comment);
3877            self.write(" ");
3878        }
3879
3880        // WITH clause
3881        if let Some(with) = &select.with {
3882            self.generate_with(with)?;
3883            if self.config.pretty {
3884                self.write_newline();
3885                self.write_indent();
3886            } else {
3887                self.write_space();
3888            }
3889        }
3890
3891        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3892        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3893        for comment in &select.post_select_comments {
3894            self.write_formatted_comment(comment);
3895            self.write(" ");
3896        }
3897
3898        self.write_keyword("SELECT");
3899
3900        // Generate query hint if present /*+ ... */
3901        if let Some(hint) = &select.hint {
3902            self.generate_hint(hint)?;
3903        }
3904
3905        // For SQL Server, convert LIMIT to TOP (structural transformation)
3906        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3907        // TOP clause (SQL Server style - before DISTINCT)
3908        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3909            && select.top.is_none()
3910            && select.limit.is_some()
3911            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3912
3913        // For TOP-supporting dialects: DISTINCT before TOP
3914        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3915        let is_top_dialect = matches!(
3916            self.config.dialect,
3917            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3918        );
3919        let keep_top_verbatim = !is_top_dialect
3920            && select.limit.is_none()
3921            && select
3922                .top
3923                .as_ref()
3924                .map_or(false, |top| top.percent || top.with_ties);
3925
3926        if select.distinct && (is_top_dialect || select.top.is_some()) {
3927            self.write_space();
3928            self.write_keyword("DISTINCT");
3929        }
3930
3931        if is_top_dialect || keep_top_verbatim {
3932            if let Some(top) = &select.top {
3933                self.write_space();
3934                self.write_keyword("TOP");
3935                if top.parenthesized {
3936                    self.write(" (");
3937                    self.generate_expression(&top.this)?;
3938                    self.write(")");
3939                } else {
3940                    self.write_space();
3941                    self.generate_expression(&top.this)?;
3942                }
3943                if top.percent {
3944                    self.write_space();
3945                    self.write_keyword("PERCENT");
3946                }
3947                if top.with_ties {
3948                    self.write_space();
3949                    self.write_keyword("WITH TIES");
3950                }
3951            } else if use_top_from_limit {
3952                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3953                if let Some(limit) = &select.limit {
3954                    self.write_space();
3955                    self.write_keyword("TOP");
3956                    // Use parentheses for complex expressions, but not for simple literals
3957                    let is_simple_literal =
3958                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3959                    if is_simple_literal {
3960                        self.write_space();
3961                        self.generate_expression(&limit.this)?;
3962                    } else {
3963                        self.write(" (");
3964                        self.generate_expression(&limit.this)?;
3965                        self.write(")");
3966                    }
3967                }
3968            }
3969        }
3970
3971        if select.distinct && !is_top_dialect && select.top.is_none() {
3972            self.write_space();
3973            self.write_keyword("DISTINCT");
3974        }
3975
3976        // DISTINCT ON clause (PostgreSQL)
3977        if let Some(distinct_on) = &select.distinct_on {
3978            self.write_space();
3979            self.write_keyword("ON");
3980            self.write(" (");
3981            for (i, expr) in distinct_on.iter().enumerate() {
3982                if i > 0 {
3983                    self.write(", ");
3984                }
3985                self.generate_expression(expr)?;
3986            }
3987            self.write(")");
3988        }
3989
3990        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3991        for modifier in &select.operation_modifiers {
3992            self.write_space();
3993            self.write_keyword(modifier);
3994        }
3995
3996        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3997        if let Some(kind) = &select.kind {
3998            self.write_space();
3999            self.write_keyword("AS");
4000            self.write_space();
4001            self.write_keyword(kind);
4002        }
4003
4004        // Expressions (only if there are any)
4005        if !select.expressions.is_empty() {
4006            if self.config.pretty {
4007                self.write_newline();
4008                self.indent_level += 1;
4009            } else {
4010                self.write_space();
4011            }
4012        }
4013
4014        for (i, expr) in select.expressions.iter().enumerate() {
4015            if i > 0 {
4016                self.write(",");
4017                if self.config.pretty {
4018                    self.write_newline();
4019                } else {
4020                    self.write_space();
4021                }
4022            }
4023            if self.config.pretty {
4024                self.write_indent();
4025            }
4026            self.generate_expression(expr)?;
4027        }
4028
4029        if self.config.pretty && !select.expressions.is_empty() {
4030            self.indent_level -= 1;
4031        }
4032
4033        // INTO clause (SELECT ... INTO table_name)
4034        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4035        if let Some(into) = &select.into {
4036            if self.config.pretty {
4037                self.write_newline();
4038                self.write_indent();
4039            } else {
4040                self.write_space();
4041            }
4042            if into.bulk_collect {
4043                self.write_keyword("BULK COLLECT INTO");
4044            } else {
4045                self.write_keyword("INTO");
4046            }
4047            if into.temporary {
4048                self.write_space();
4049                self.write_keyword("TEMPORARY");
4050            }
4051            if into.unlogged {
4052                self.write_space();
4053                self.write_keyword("UNLOGGED");
4054            }
4055            self.write_space();
4056            // If we have multiple expressions, output them comma-separated
4057            if !into.expressions.is_empty() {
4058                for (i, expr) in into.expressions.iter().enumerate() {
4059                    if i > 0 {
4060                        self.write(", ");
4061                    }
4062                    self.generate_expression(expr)?;
4063                }
4064            } else {
4065                self.generate_expression(&into.this)?;
4066            }
4067        }
4068
4069        // FROM clause
4070        if let Some(from) = &select.from {
4071            if self.config.pretty {
4072                self.write_newline();
4073                self.write_indent();
4074            } else {
4075                self.write_space();
4076            }
4077            self.write_keyword("FROM");
4078            self.write_space();
4079
4080            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4081            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4082            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4083            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4084            let has_tablesample = from
4085                .expressions
4086                .iter()
4087                .any(|e| matches!(e, Expression::TableSample(_)));
4088            let is_cross_join_dialect = matches!(
4089                self.config.dialect,
4090                Some(DialectType::BigQuery)
4091                    | Some(DialectType::Hive)
4092                    | Some(DialectType::Spark)
4093                    | Some(DialectType::Databricks)
4094                    | Some(DialectType::SQLite)
4095                    | Some(DialectType::ClickHouse)
4096            );
4097            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4098            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4099            let source_is_same_as_target = self.config.source_dialect.is_some()
4100                && self.config.source_dialect == self.config.dialect;
4101            let source_is_cross_join_dialect = matches!(
4102                self.config.source_dialect,
4103                Some(DialectType::BigQuery)
4104                    | Some(DialectType::Hive)
4105                    | Some(DialectType::Spark)
4106                    | Some(DialectType::Databricks)
4107                    | Some(DialectType::SQLite)
4108                    | Some(DialectType::ClickHouse)
4109            );
4110            let use_cross_join = !has_tablesample
4111                && is_cross_join_dialect
4112                && (source_is_same_as_target
4113                    || source_is_cross_join_dialect
4114                    || self.config.source_dialect.is_none());
4115
4116            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4117            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4118
4119            for (i, expr) in from.expressions.iter().enumerate() {
4120                if i > 0 {
4121                    if use_cross_join {
4122                        self.write(" CROSS JOIN ");
4123                    } else {
4124                        self.write(", ");
4125                    }
4126                }
4127                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4128                    self.write("(");
4129                    self.generate_expression(expr)?;
4130                    self.write(")");
4131                } else {
4132                    self.generate_expression(expr)?;
4133                }
4134            }
4135        }
4136
4137        // JOINs - handle nested join structure for pretty printing
4138        // Deferred-condition joins "own" the non-deferred joins that follow them
4139        // until the next deferred join or end of list
4140        if self.config.pretty {
4141            self.generate_joins_with_nesting(&select.joins)?;
4142        } else {
4143            for join in &select.joins {
4144                self.generate_join(join)?;
4145            }
4146            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4147            for join in select.joins.iter().rev() {
4148                if join.deferred_condition {
4149                    self.generate_join_condition(join)?;
4150                }
4151            }
4152        }
4153
4154        // LATERAL VIEW clauses (Hive/Spark)
4155        for lateral_view in &select.lateral_views {
4156            self.generate_lateral_view(lateral_view)?;
4157        }
4158
4159        // PREWHERE (ClickHouse)
4160        if let Some(prewhere) = &select.prewhere {
4161            self.write_clause_condition("PREWHERE", prewhere)?;
4162        }
4163
4164        // WHERE
4165        if let Some(where_clause) = &select.where_clause {
4166            self.write_clause_condition("WHERE", &where_clause.this)?;
4167        }
4168
4169        // CONNECT BY (Oracle hierarchical queries)
4170        if let Some(connect) = &select.connect {
4171            self.generate_connect(connect)?;
4172        }
4173
4174        // GROUP BY
4175        if let Some(group_by) = &select.group_by {
4176            if self.config.pretty {
4177                // Output leading comments on their own lines before GROUP BY
4178                for comment in &group_by.comments {
4179                    self.write_newline();
4180                    self.write_indent();
4181                    self.write_formatted_comment(comment);
4182                }
4183                self.write_newline();
4184                self.write_indent();
4185            } else {
4186                self.write_space();
4187                // In non-pretty mode, output comments inline
4188                for comment in &group_by.comments {
4189                    self.write_formatted_comment(comment);
4190                    self.write_space();
4191                }
4192            }
4193            self.write_keyword("GROUP BY");
4194            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4195            match group_by.all {
4196                Some(true) => {
4197                    self.write_space();
4198                    self.write_keyword("ALL");
4199                }
4200                Some(false) => {
4201                    self.write_space();
4202                    self.write_keyword("DISTINCT");
4203                }
4204                None => {}
4205            }
4206            if !group_by.expressions.is_empty() {
4207                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4208                // These are represented as Cube/Rollup expressions with empty expressions at the end
4209                let mut trailing_cube = false;
4210                let mut trailing_rollup = false;
4211                let mut plain_expressions: Vec<&Expression> = Vec::new();
4212                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4213                let mut cube_expressions: Vec<&Expression> = Vec::new();
4214                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4215
4216                for expr in &group_by.expressions {
4217                    match expr {
4218                        Expression::Cube(c) if c.expressions.is_empty() => {
4219                            trailing_cube = true;
4220                        }
4221                        Expression::Rollup(r) if r.expressions.is_empty() => {
4222                            trailing_rollup = true;
4223                        }
4224                        Expression::Function(f) if f.name == "CUBE" => {
4225                            cube_expressions.push(expr);
4226                        }
4227                        Expression::Function(f) if f.name == "ROLLUP" => {
4228                            rollup_expressions.push(expr);
4229                        }
4230                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4231                            grouping_sets_expressions.push(expr);
4232                        }
4233                        _ => {
4234                            plain_expressions.push(expr);
4235                        }
4236                    }
4237                }
4238
4239                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4240                let mut regular_expressions: Vec<&Expression> = Vec::new();
4241                regular_expressions.extend(plain_expressions);
4242                regular_expressions.extend(grouping_sets_expressions);
4243                regular_expressions.extend(cube_expressions);
4244                regular_expressions.extend(rollup_expressions);
4245
4246                if self.config.pretty {
4247                    self.write_newline();
4248                    self.indent_level += 1;
4249                    self.write_indent();
4250                } else {
4251                    self.write_space();
4252                }
4253
4254                for (i, expr) in regular_expressions.iter().enumerate() {
4255                    if i > 0 {
4256                        if self.config.pretty {
4257                            self.write(",");
4258                            self.write_newline();
4259                            self.write_indent();
4260                        } else {
4261                            self.write(", ");
4262                        }
4263                    }
4264                    self.generate_expression(expr)?;
4265                }
4266
4267                if self.config.pretty {
4268                    self.indent_level -= 1;
4269                }
4270
4271                // Output trailing WITH CUBE or WITH ROLLUP
4272                if trailing_cube {
4273                    self.write_space();
4274                    self.write_keyword("WITH CUBE");
4275                } else if trailing_rollup {
4276                    self.write_space();
4277                    self.write_keyword("WITH ROLLUP");
4278                }
4279            }
4280
4281            // ClickHouse: WITH TOTALS
4282            if group_by.totals {
4283                self.write_space();
4284                self.write_keyword("WITH TOTALS");
4285            }
4286        }
4287
4288        // HAVING
4289        if let Some(having) = &select.having {
4290            if self.config.pretty {
4291                // Output leading comments on their own lines before HAVING
4292                for comment in &having.comments {
4293                    self.write_newline();
4294                    self.write_indent();
4295                    self.write_formatted_comment(comment);
4296                }
4297            } else {
4298                for comment in &having.comments {
4299                    self.write_space();
4300                    self.write_formatted_comment(comment);
4301                }
4302            }
4303            self.write_clause_condition("HAVING", &having.this)?;
4304        }
4305
4306        // QUALIFY and WINDOW clause ordering depends on input SQL
4307        if select.qualify_after_window {
4308            // WINDOW before QUALIFY (DuckDB style)
4309            if let Some(windows) = &select.windows {
4310                self.write_window_clause(windows)?;
4311            }
4312            if let Some(qualify) = &select.qualify {
4313                self.write_clause_condition("QUALIFY", &qualify.this)?;
4314            }
4315        } else {
4316            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4317            if let Some(qualify) = &select.qualify {
4318                self.write_clause_condition("QUALIFY", &qualify.this)?;
4319            }
4320            if let Some(windows) = &select.windows {
4321                self.write_window_clause(windows)?;
4322            }
4323        }
4324
4325        // DISTRIBUTE BY (Hive/Spark)
4326        if let Some(distribute_by) = &select.distribute_by {
4327            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4328        }
4329
4330        // CLUSTER BY (Hive/Spark)
4331        if let Some(cluster_by) = &select.cluster_by {
4332            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4333        }
4334
4335        // SORT BY (Hive/Spark - comes before ORDER BY)
4336        if let Some(sort_by) = &select.sort_by {
4337            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4338        }
4339
4340        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4341        if let Some(order_by) = &select.order_by {
4342            if self.config.pretty {
4343                // Output leading comments on their own lines before ORDER BY
4344                for comment in &order_by.comments {
4345                    self.write_newline();
4346                    self.write_indent();
4347                    self.write_formatted_comment(comment);
4348                }
4349            } else {
4350                for comment in &order_by.comments {
4351                    self.write_space();
4352                    self.write_formatted_comment(comment);
4353                }
4354            }
4355            let keyword = if order_by.siblings {
4356                "ORDER SIBLINGS BY"
4357            } else {
4358                "ORDER BY"
4359            };
4360            self.write_order_clause(keyword, &order_by.expressions)?;
4361        }
4362
4363        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4364        if select.order_by.is_none()
4365            && select.fetch.is_some()
4366            && matches!(
4367                self.config.dialect,
4368                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4369            )
4370        {
4371            if self.config.pretty {
4372                self.write_newline();
4373                self.write_indent();
4374            } else {
4375                self.write_space();
4376            }
4377            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4378        }
4379
4380        // LIMIT and OFFSET
4381        // PostgreSQL and others use: LIMIT count OFFSET offset
4382        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4383        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4384        let is_presto_like = matches!(
4385            self.config.dialect,
4386            Some(DialectType::Presto) | Some(DialectType::Trino)
4387        );
4388
4389        if is_presto_like && select.offset.is_some() {
4390            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4391            if let Some(offset) = &select.offset {
4392                if self.config.pretty {
4393                    self.write_newline();
4394                    self.write_indent();
4395                } else {
4396                    self.write_space();
4397                }
4398                self.write_keyword("OFFSET");
4399                self.write_space();
4400                self.write_limit_expr(&offset.this)?;
4401                if offset.rows == Some(true) {
4402                    self.write_space();
4403                    self.write_keyword("ROWS");
4404                }
4405            }
4406            if let Some(limit) = &select.limit {
4407                if self.config.pretty {
4408                    self.write_newline();
4409                    self.write_indent();
4410                } else {
4411                    self.write_space();
4412                }
4413                self.write_keyword("LIMIT");
4414                self.write_space();
4415                self.write_limit_expr(&limit.this)?;
4416                if limit.percent {
4417                    self.write_space();
4418                    self.write_keyword("PERCENT");
4419                }
4420                // Emit any comments that were captured from before the LIMIT keyword
4421                for comment in &limit.comments {
4422                    self.write(" ");
4423                    self.write_formatted_comment(comment);
4424                }
4425            }
4426        } else {
4427            // Check if FETCH will be converted to LIMIT (used for ordering)
4428            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4429                !fetch.percent
4430                    && !fetch.with_ties
4431                    && fetch.count.is_some()
4432                    && matches!(
4433                        self.config.dialect,
4434                        Some(DialectType::Spark)
4435                            | Some(DialectType::Hive)
4436                            | Some(DialectType::DuckDB)
4437                            | Some(DialectType::SQLite)
4438                            | Some(DialectType::MySQL)
4439                            | Some(DialectType::BigQuery)
4440                            | Some(DialectType::Databricks)
4441                            | Some(DialectType::StarRocks)
4442                            | Some(DialectType::Doris)
4443                            | Some(DialectType::Athena)
4444                            | Some(DialectType::ClickHouse)
4445                            | Some(DialectType::Redshift)
4446                    )
4447            });
4448
4449            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4450            if let Some(limit) = &select.limit {
4451                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4452                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4453                    if self.config.pretty {
4454                        self.write_newline();
4455                        self.write_indent();
4456                    } else {
4457                        self.write_space();
4458                    }
4459                    self.write_keyword("LIMIT");
4460                    self.write_space();
4461                    self.write_limit_expr(&limit.this)?;
4462                    if limit.percent {
4463                        self.write_space();
4464                        self.write_keyword("PERCENT");
4465                    }
4466                    // Emit any comments that were captured from before the LIMIT keyword
4467                    for comment in &limit.comments {
4468                        self.write(" ");
4469                        self.write_formatted_comment(comment);
4470                    }
4471                }
4472            }
4473
4474            // Convert TOP to LIMIT for non-TOP dialects
4475            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4476                if let Some(top) = &select.top {
4477                    if !top.percent && !top.with_ties {
4478                        if self.config.pretty {
4479                            self.write_newline();
4480                            self.write_indent();
4481                        } else {
4482                            self.write_space();
4483                        }
4484                        self.write_keyword("LIMIT");
4485                        self.write_space();
4486                        self.generate_expression(&top.this)?;
4487                    }
4488                }
4489            }
4490
4491            // If FETCH will be converted to LIMIT and there's also OFFSET,
4492            // emit LIMIT from FETCH BEFORE the OFFSET
4493            if fetch_as_limit && select.offset.is_some() {
4494                if let Some(fetch) = &select.fetch {
4495                    if self.config.pretty {
4496                        self.write_newline();
4497                        self.write_indent();
4498                    } else {
4499                        self.write_space();
4500                    }
4501                    self.write_keyword("LIMIT");
4502                    self.write_space();
4503                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4504                }
4505            }
4506
4507            // OFFSET
4508            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4509            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4510            if let Some(offset) = &select.offset {
4511                if self.config.pretty {
4512                    self.write_newline();
4513                    self.write_indent();
4514                } else {
4515                    self.write_space();
4516                }
4517                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4518                    // SQL Server 2012+ OFFSET ... FETCH syntax
4519                    self.write_keyword("OFFSET");
4520                    self.write_space();
4521                    self.write_limit_expr(&offset.this)?;
4522                    self.write_space();
4523                    self.write_keyword("ROWS");
4524                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4525                    if let Some(limit) = &select.limit {
4526                        self.write_space();
4527                        self.write_keyword("FETCH NEXT");
4528                        self.write_space();
4529                        self.write_limit_expr(&limit.this)?;
4530                        self.write_space();
4531                        self.write_keyword("ROWS ONLY");
4532                    }
4533                } else {
4534                    self.write_keyword("OFFSET");
4535                    self.write_space();
4536                    self.write_limit_expr(&offset.this)?;
4537                    // Output ROWS keyword if it was in the original SQL
4538                    if offset.rows == Some(true) {
4539                        self.write_space();
4540                        self.write_keyword("ROWS");
4541                    }
4542                }
4543            }
4544        }
4545
4546        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4547        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4548            if let Some(limit_by) = &select.limit_by {
4549                if !limit_by.is_empty() {
4550                    self.write_space();
4551                    self.write_keyword("BY");
4552                    self.write_space();
4553                    for (i, expr) in limit_by.iter().enumerate() {
4554                        if i > 0 {
4555                            self.write(", ");
4556                        }
4557                        self.generate_expression(expr)?;
4558                    }
4559                }
4560            }
4561        }
4562
4563        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4564        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4565            if let Some(settings) = &select.settings {
4566                if self.config.pretty {
4567                    self.write_newline();
4568                    self.write_indent();
4569                } else {
4570                    self.write_space();
4571                }
4572                self.write_keyword("SETTINGS");
4573                self.write_space();
4574                for (i, expr) in settings.iter().enumerate() {
4575                    if i > 0 {
4576                        self.write(", ");
4577                    }
4578                    self.generate_expression(expr)?;
4579                }
4580            }
4581
4582            if let Some(format_expr) = &select.format {
4583                if self.config.pretty {
4584                    self.write_newline();
4585                    self.write_indent();
4586                } else {
4587                    self.write_space();
4588                }
4589                self.write_keyword("FORMAT");
4590                self.write_space();
4591                self.generate_expression(format_expr)?;
4592            }
4593        }
4594
4595        // FETCH FIRST/NEXT
4596        if let Some(fetch) = &select.fetch {
4597            // Check if we already emitted LIMIT from FETCH before OFFSET
4598            let fetch_already_as_limit = select.offset.is_some()
4599                && !fetch.percent
4600                && !fetch.with_ties
4601                && fetch.count.is_some()
4602                && matches!(
4603                    self.config.dialect,
4604                    Some(DialectType::Spark)
4605                        | Some(DialectType::Hive)
4606                        | Some(DialectType::DuckDB)
4607                        | Some(DialectType::SQLite)
4608                        | Some(DialectType::MySQL)
4609                        | Some(DialectType::BigQuery)
4610                        | Some(DialectType::Databricks)
4611                        | Some(DialectType::StarRocks)
4612                        | Some(DialectType::Doris)
4613                        | Some(DialectType::Athena)
4614                        | Some(DialectType::ClickHouse)
4615                        | Some(DialectType::Redshift)
4616                );
4617
4618            if fetch_already_as_limit {
4619                // Already emitted as LIMIT before OFFSET, skip
4620            } else {
4621                if self.config.pretty {
4622                    self.write_newline();
4623                    self.write_indent();
4624                } else {
4625                    self.write_space();
4626                }
4627
4628                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4629                let use_limit = !fetch.percent
4630                    && !fetch.with_ties
4631                    && fetch.count.is_some()
4632                    && matches!(
4633                        self.config.dialect,
4634                        Some(DialectType::Spark)
4635                            | Some(DialectType::Hive)
4636                            | Some(DialectType::DuckDB)
4637                            | Some(DialectType::SQLite)
4638                            | Some(DialectType::MySQL)
4639                            | Some(DialectType::BigQuery)
4640                            | Some(DialectType::Databricks)
4641                            | Some(DialectType::StarRocks)
4642                            | Some(DialectType::Doris)
4643                            | Some(DialectType::Athena)
4644                            | Some(DialectType::ClickHouse)
4645                            | Some(DialectType::Redshift)
4646                    );
4647
4648                if use_limit {
4649                    self.write_keyword("LIMIT");
4650                    self.write_space();
4651                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4652                } else {
4653                    self.write_keyword("FETCH");
4654                    self.write_space();
4655                    self.write_keyword(&fetch.direction);
4656                    if let Some(ref count) = fetch.count {
4657                        self.write_space();
4658                        self.generate_expression(count)?;
4659                    }
4660                    if fetch.percent {
4661                        self.write_space();
4662                        self.write_keyword("PERCENT");
4663                    }
4664                    if fetch.rows {
4665                        self.write_space();
4666                        self.write_keyword("ROWS");
4667                    }
4668                    if fetch.with_ties {
4669                        self.write_space();
4670                        self.write_keyword("WITH TIES");
4671                    } else {
4672                        self.write_space();
4673                        self.write_keyword("ONLY");
4674                    }
4675                }
4676            } // close fetch_already_as_limit else
4677        }
4678
4679        // SAMPLE / TABLESAMPLE
4680        if let Some(sample) = &select.sample {
4681            use crate::dialects::DialectType;
4682            if self.config.pretty {
4683                self.write_newline();
4684            } else {
4685                self.write_space();
4686            }
4687
4688            if sample.is_using_sample {
4689                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4690                self.write_keyword("USING SAMPLE");
4691                self.generate_sample_body(sample)?;
4692            } else {
4693                self.write_keyword("TABLESAMPLE");
4694
4695                // Snowflake defaults to BERNOULLI when no explicit method is given
4696                let snowflake_bernoulli =
4697                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4698                        && !sample.explicit_method;
4699                if snowflake_bernoulli {
4700                    self.write_space();
4701                    self.write_keyword("BERNOULLI");
4702                }
4703
4704                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4705                if matches!(sample.method, SampleMethod::Bucket) {
4706                    self.write_space();
4707                    self.write("(");
4708                    self.write_keyword("BUCKET");
4709                    self.write_space();
4710                    if let Some(ref num) = sample.bucket_numerator {
4711                        self.generate_expression(num)?;
4712                    }
4713                    self.write_space();
4714                    self.write_keyword("OUT OF");
4715                    self.write_space();
4716                    if let Some(ref denom) = sample.bucket_denominator {
4717                        self.generate_expression(denom)?;
4718                    }
4719                    if let Some(ref field) = sample.bucket_field {
4720                        self.write_space();
4721                        self.write_keyword("ON");
4722                        self.write_space();
4723                        self.generate_expression(field)?;
4724                    }
4725                    self.write(")");
4726                } else if sample.unit_after_size {
4727                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4728                    if sample.explicit_method && sample.method_before_size {
4729                        self.write_space();
4730                        match sample.method {
4731                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4732                            SampleMethod::System => self.write_keyword("SYSTEM"),
4733                            SampleMethod::Block => self.write_keyword("BLOCK"),
4734                            SampleMethod::Row => self.write_keyword("ROW"),
4735                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4736                            _ => {}
4737                        }
4738                    }
4739                    self.write(" (");
4740                    self.generate_expression(&sample.size)?;
4741                    self.write_space();
4742                    match sample.method {
4743                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4744                        SampleMethod::Row => self.write_keyword("ROWS"),
4745                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4746                        _ => {
4747                            self.write_keyword("PERCENT");
4748                        }
4749                    }
4750                    self.write(")");
4751                } else {
4752                    // Syntax: TABLESAMPLE METHOD (size)
4753                    self.write_space();
4754                    match sample.method {
4755                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4756                        SampleMethod::System => self.write_keyword("SYSTEM"),
4757                        SampleMethod::Block => self.write_keyword("BLOCK"),
4758                        SampleMethod::Row => self.write_keyword("ROW"),
4759                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4760                        SampleMethod::Bucket => {}
4761                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4762                    }
4763                    self.write(" (");
4764                    self.generate_expression(&sample.size)?;
4765                    if matches!(sample.method, SampleMethod::Percent) {
4766                        self.write_space();
4767                        self.write_keyword("PERCENT");
4768                    }
4769                    self.write(")");
4770                }
4771            }
4772
4773            if let Some(seed) = &sample.seed {
4774                self.write_space();
4775                // Databricks/Spark use REPEATABLE, not SEED
4776                let use_seed = sample.use_seed_keyword
4777                    && !matches!(
4778                        self.config.dialect,
4779                        Some(crate::dialects::DialectType::Databricks)
4780                            | Some(crate::dialects::DialectType::Spark)
4781                    );
4782                if use_seed {
4783                    self.write_keyword("SEED");
4784                } else {
4785                    self.write_keyword("REPEATABLE");
4786                }
4787                self.write(" (");
4788                self.generate_expression(seed)?;
4789                self.write(")");
4790            }
4791        }
4792
4793        // FOR UPDATE/SHARE locks
4794        // Skip locking clauses for dialects that don't support them
4795        if self.config.locking_reads_supported {
4796            for lock in &select.locks {
4797                if self.config.pretty {
4798                    self.write_newline();
4799                    self.write_indent();
4800                } else {
4801                    self.write_space();
4802                }
4803                self.generate_lock(lock)?;
4804            }
4805        }
4806
4807        // FOR XML clause (T-SQL)
4808        if !select.for_xml.is_empty() {
4809            if self.config.pretty {
4810                self.write_newline();
4811                self.write_indent();
4812            } else {
4813                self.write_space();
4814            }
4815            self.write_keyword("FOR XML");
4816            for (i, opt) in select.for_xml.iter().enumerate() {
4817                if self.config.pretty {
4818                    if i > 0 {
4819                        self.write(",");
4820                    }
4821                    self.write_newline();
4822                    self.write_indent();
4823                    self.write("  "); // extra indent for options
4824                } else {
4825                    if i > 0 {
4826                        self.write(",");
4827                    }
4828                    self.write_space();
4829                }
4830                self.generate_for_xml_option(opt)?;
4831            }
4832        }
4833
4834        // TSQL: OPTION clause
4835        if let Some(ref option) = select.option {
4836            if matches!(
4837                self.config.dialect,
4838                Some(crate::dialects::DialectType::TSQL)
4839                    | Some(crate::dialects::DialectType::Fabric)
4840            ) {
4841                self.write_space();
4842                self.write(option);
4843            }
4844        }
4845
4846        Ok(())
4847    }
4848
4849    /// Generate a single FOR XML option
4850    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4851        match opt {
4852            Expression::QueryOption(qo) => {
4853                // Extract the option name from Var
4854                if let Expression::Var(var) = &*qo.this {
4855                    self.write(&var.this);
4856                } else {
4857                    self.generate_expression(&qo.this)?;
4858                }
4859                // If there's an expression (like PATH('element')), output it in parens
4860                if let Some(expr) = &qo.expression {
4861                    self.write("(");
4862                    self.generate_expression(expr)?;
4863                    self.write(")");
4864                }
4865            }
4866            _ => {
4867                self.generate_expression(opt)?;
4868            }
4869        }
4870        Ok(())
4871    }
4872
4873    fn generate_with(&mut self, with: &With) -> Result<()> {
4874        use crate::dialects::DialectType;
4875
4876        // Output leading comments before WITH
4877        for comment in &with.leading_comments {
4878            self.write_formatted_comment(comment);
4879            self.write(" ");
4880        }
4881        self.write_keyword("WITH");
4882        if with.recursive && self.config.cte_recursive_keyword_required {
4883            self.write_space();
4884            self.write_keyword("RECURSIVE");
4885        }
4886        self.write_space();
4887
4888        // BigQuery doesn't support column aliases in CTE definitions
4889        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4890
4891        for (i, cte) in with.ctes.iter().enumerate() {
4892            if i > 0 {
4893                self.write(",");
4894                if self.config.pretty {
4895                    self.write_space();
4896                } else {
4897                    self.write(" ");
4898                }
4899            }
4900            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4901                self.generate_expression(&cte.this)?;
4902                self.write_space();
4903                self.write_keyword("AS");
4904                self.write_space();
4905                self.generate_identifier(&cte.alias)?;
4906                continue;
4907            }
4908            self.generate_identifier(&cte.alias)?;
4909            // Output CTE comments after alias name, before AS
4910            for comment in &cte.comments {
4911                self.write_space();
4912                self.write_formatted_comment(comment);
4913            }
4914            if !cte.columns.is_empty() && !skip_cte_columns {
4915                self.write("(");
4916                for (j, col) in cte.columns.iter().enumerate() {
4917                    if j > 0 {
4918                        self.write(", ");
4919                    }
4920                    self.generate_identifier(col)?;
4921                }
4922                self.write(")");
4923            }
4924            // USING KEY (columns) for DuckDB recursive CTEs
4925            if !cte.key_expressions.is_empty() {
4926                self.write_space();
4927                self.write_keyword("USING KEY");
4928                self.write(" (");
4929                for (i, key) in cte.key_expressions.iter().enumerate() {
4930                    if i > 0 {
4931                        self.write(", ");
4932                    }
4933                    self.generate_identifier(key)?;
4934                }
4935                self.write(")");
4936            }
4937            self.write_space();
4938            self.write_keyword("AS");
4939            // MATERIALIZED / NOT MATERIALIZED
4940            if let Some(materialized) = cte.materialized {
4941                self.write_space();
4942                if materialized {
4943                    self.write_keyword("MATERIALIZED");
4944                } else {
4945                    self.write_keyword("NOT MATERIALIZED");
4946                }
4947            }
4948            self.write(" (");
4949            if self.config.pretty {
4950                self.write_newline();
4951                self.indent_level += 1;
4952                self.write_indent();
4953            }
4954            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4955            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4956            let wrap_values_in_select = matches!(
4957                self.config.dialect,
4958                Some(DialectType::Spark) | Some(DialectType::Databricks)
4959            ) && matches!(&cte.this, Expression::Values(_));
4960
4961            if wrap_values_in_select {
4962                self.write_keyword("SELECT");
4963                self.write(" * ");
4964                self.write_keyword("FROM");
4965                self.write_space();
4966            }
4967            self.generate_expression(&cte.this)?;
4968            if self.config.pretty {
4969                self.write_newline();
4970                self.indent_level -= 1;
4971                self.write_indent();
4972            }
4973            self.write(")");
4974        }
4975
4976        // Generate SEARCH/CYCLE clause if present
4977        if let Some(search) = &with.search {
4978            self.write_space();
4979            self.generate_expression(search)?;
4980        }
4981
4982        Ok(())
4983    }
4984
4985    /// Generate joins with proper nesting structure for pretty printing.
4986    /// Deferred-condition joins "own" the non-deferred joins that follow them
4987    /// within the same nesting_group.
4988    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4989        let mut i = 0;
4990        while i < joins.len() {
4991            if joins[i].deferred_condition {
4992                let parent_group = joins[i].nesting_group;
4993
4994                // This join owns the following non-deferred joins in the same nesting_group
4995                // First output the join keyword and table (without condition)
4996                self.generate_join_without_condition(&joins[i])?;
4997
4998                // Find the range of child joins: same nesting_group and not deferred
4999                let child_start = i + 1;
5000                let mut child_end = child_start;
5001                while child_end < joins.len()
5002                    && !joins[child_end].deferred_condition
5003                    && joins[child_end].nesting_group == parent_group
5004                {
5005                    child_end += 1;
5006                }
5007
5008                // Output child joins with extra indentation
5009                if child_start < child_end {
5010                    self.indent_level += 1;
5011                    for j in child_start..child_end {
5012                        self.generate_join(&joins[j])?;
5013                    }
5014                    self.indent_level -= 1;
5015                }
5016
5017                // Output the deferred condition at the parent level
5018                self.generate_join_condition(&joins[i])?;
5019
5020                i = child_end;
5021            } else {
5022                // Regular join (no nesting)
5023                self.generate_join(&joins[i])?;
5024                i += 1;
5025            }
5026        }
5027        Ok(())
5028    }
5029
5030    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5031    /// Used for deferred-condition joins where the condition is output after child joins.
5032    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5033        // Save and temporarily clear the condition to prevent generate_join from outputting it
5034        // We achieve this by creating a modified copy
5035        let mut join_copy = join.clone();
5036        join_copy.on = None;
5037        join_copy.using = Vec::new();
5038        join_copy.deferred_condition = false;
5039        self.generate_join(&join_copy)
5040    }
5041
5042    fn generate_join(&mut self, join: &Join) -> Result<()> {
5043        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5044        if join.kind == JoinKind::Implicit {
5045            self.write(",");
5046            if self.config.pretty {
5047                self.write_newline();
5048                self.write_indent();
5049            } else {
5050                self.write_space();
5051            }
5052            self.generate_expression(&join.this)?;
5053            return Ok(());
5054        }
5055
5056        if self.config.pretty {
5057            self.write_newline();
5058            self.write_indent();
5059        } else {
5060            self.write_space();
5061        }
5062
5063        // Helper: format hint suffix (e.g., " LOOP" or "")
5064        // Only include join hints for dialects that support them
5065        let hint_str = if self.config.join_hints {
5066            join.join_hint
5067                .as_ref()
5068                .map(|h| format!(" {}", h))
5069                .unwrap_or_default()
5070        } else {
5071            String::new()
5072        };
5073
5074        let clickhouse_join_keyword =
5075            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5076                if let Some(hint) = &join.join_hint {
5077                    let mut global = false;
5078                    let mut strictness: Option<&'static str> = None;
5079                    for part in hint.split_whitespace() {
5080                        match part.to_uppercase().as_str() {
5081                            "GLOBAL" => global = true,
5082                            "ANY" => strictness = Some("ANY"),
5083                            "ASOF" => strictness = Some("ASOF"),
5084                            "SEMI" => strictness = Some("SEMI"),
5085                            "ANTI" => strictness = Some("ANTI"),
5086                            _ => {}
5087                        }
5088                    }
5089
5090                    if global || strictness.is_some() {
5091                        let join_type = match join.kind {
5092                            JoinKind::Left => {
5093                                if join.use_outer_keyword {
5094                                    "LEFT OUTER"
5095                                } else if join.use_inner_keyword {
5096                                    "LEFT INNER"
5097                                } else {
5098                                    "LEFT"
5099                                }
5100                            }
5101                            JoinKind::Right => {
5102                                if join.use_outer_keyword {
5103                                    "RIGHT OUTER"
5104                                } else if join.use_inner_keyword {
5105                                    "RIGHT INNER"
5106                                } else {
5107                                    "RIGHT"
5108                                }
5109                            }
5110                            JoinKind::Full => {
5111                                if join.use_outer_keyword {
5112                                    "FULL OUTER"
5113                                } else {
5114                                    "FULL"
5115                                }
5116                            }
5117                            JoinKind::Inner => {
5118                                if join.use_inner_keyword {
5119                                    "INNER"
5120                                } else {
5121                                    ""
5122                                }
5123                            }
5124                            _ => "",
5125                        };
5126
5127                        let mut parts = Vec::new();
5128                        if global {
5129                            parts.push("GLOBAL");
5130                        }
5131                        if !join_type.is_empty() {
5132                            parts.push(join_type);
5133                        }
5134                        if let Some(strict) = strictness {
5135                            parts.push(strict);
5136                        }
5137                        parts.push("JOIN");
5138                        Some(parts.join(" "))
5139                    } else {
5140                        None
5141                    }
5142                } else {
5143                    None
5144                }
5145            } else {
5146                None
5147            };
5148
5149        // Output any comments associated with this join
5150        // In pretty mode, comments go on their own line before the join keyword
5151        // In non-pretty mode, comments go inline before the join keyword
5152        if !join.comments.is_empty() {
5153            if self.config.pretty {
5154                // In pretty mode, go back before the newline+indent we just wrote
5155                // and output comments on their own lines
5156                // We need to output comments BEFORE the join keyword on separate lines
5157                // Trim the trailing newline+indent we already wrote
5158                let trimmed = self.output.trim_end().len();
5159                self.output.truncate(trimmed);
5160                for comment in &join.comments {
5161                    self.write_newline();
5162                    self.write_indent();
5163                    self.write_formatted_comment(comment);
5164                }
5165                self.write_newline();
5166                self.write_indent();
5167            } else {
5168                for comment in &join.comments {
5169                    self.write_formatted_comment(comment);
5170                    self.write_space();
5171                }
5172            }
5173        }
5174
5175        let directed_str = if join.directed { " DIRECTED" } else { "" };
5176
5177        if let Some(keyword) = clickhouse_join_keyword {
5178            self.write_keyword(&keyword);
5179        } else {
5180            match join.kind {
5181                JoinKind::Inner => {
5182                    if join.use_inner_keyword {
5183                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5184                    } else {
5185                        self.write_keyword(&format!(
5186                            "{}{}JOIN",
5187                            if hint_str.is_empty() {
5188                                String::new()
5189                            } else {
5190                                format!("{} ", hint_str.trim())
5191                            },
5192                            if directed_str.is_empty() {
5193                                ""
5194                            } else {
5195                                "DIRECTED "
5196                            }
5197                        ));
5198                    }
5199                }
5200                JoinKind::Left => {
5201                    if join.use_outer_keyword {
5202                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5203                    } else if join.use_inner_keyword {
5204                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5205                    } else {
5206                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5207                    }
5208                }
5209                JoinKind::Right => {
5210                    if join.use_outer_keyword {
5211                        self.write_keyword(&format!(
5212                            "RIGHT OUTER{}{} JOIN",
5213                            hint_str, directed_str
5214                        ));
5215                    } else if join.use_inner_keyword {
5216                        self.write_keyword(&format!(
5217                            "RIGHT INNER{}{} JOIN",
5218                            hint_str, directed_str
5219                        ));
5220                    } else {
5221                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5222                    }
5223                }
5224                JoinKind::Full => {
5225                    if join.use_outer_keyword {
5226                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5227                    } else {
5228                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5229                    }
5230                }
5231                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5232                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5233                JoinKind::Natural => {
5234                    if join.use_inner_keyword {
5235                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5236                    } else {
5237                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5238                    }
5239                }
5240                JoinKind::NaturalLeft => {
5241                    if join.use_outer_keyword {
5242                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5243                    } else {
5244                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5245                    }
5246                }
5247                JoinKind::NaturalRight => {
5248                    if join.use_outer_keyword {
5249                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5250                    } else {
5251                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5252                    }
5253                }
5254                JoinKind::NaturalFull => {
5255                    if join.use_outer_keyword {
5256                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5257                    } else {
5258                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5259                    }
5260                }
5261                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5262                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5263                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5264                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5265                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5266                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5267                JoinKind::CrossApply => {
5268                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5269                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5270                        self.write_keyword("CROSS APPLY");
5271                    } else {
5272                        self.write_keyword("INNER JOIN LATERAL");
5273                    }
5274                }
5275                JoinKind::OuterApply => {
5276                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5277                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5278                        self.write_keyword("OUTER APPLY");
5279                    } else {
5280                        self.write_keyword("LEFT JOIN LATERAL");
5281                    }
5282                }
5283                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5284                JoinKind::AsOfLeft => {
5285                    if join.use_outer_keyword {
5286                        self.write_keyword("ASOF LEFT OUTER JOIN");
5287                    } else {
5288                        self.write_keyword("ASOF LEFT JOIN");
5289                    }
5290                }
5291                JoinKind::AsOfRight => {
5292                    if join.use_outer_keyword {
5293                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5294                    } else {
5295                        self.write_keyword("ASOF RIGHT JOIN");
5296                    }
5297                }
5298                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5299                JoinKind::LeftLateral => {
5300                    if join.use_outer_keyword {
5301                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5302                    } else {
5303                        self.write_keyword("LEFT LATERAL JOIN");
5304                    }
5305                }
5306                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5307                JoinKind::Implicit => {
5308                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5309                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5310                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5311                    use crate::dialects::DialectType;
5312                    let is_cj_dialect = matches!(
5313                        self.config.dialect,
5314                        Some(DialectType::BigQuery)
5315                            | Some(DialectType::Hive)
5316                            | Some(DialectType::Spark)
5317                            | Some(DialectType::Databricks)
5318                    );
5319                    let source_is_same = self.config.source_dialect.is_some()
5320                        && self.config.source_dialect == self.config.dialect;
5321                    let source_is_cj = matches!(
5322                        self.config.source_dialect,
5323                        Some(DialectType::BigQuery)
5324                            | Some(DialectType::Hive)
5325                            | Some(DialectType::Spark)
5326                            | Some(DialectType::Databricks)
5327                    );
5328                    if is_cj_dialect
5329                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5330                    {
5331                        self.write_keyword("CROSS JOIN");
5332                    } else {
5333                        // Implicit join uses comma: FROM a, b
5334                        // We already wrote a space before the match, so replace with comma
5335                        // by removing trailing space and writing ", "
5336                        self.output.truncate(self.output.trim_end().len());
5337                        self.write(",");
5338                    }
5339                }
5340                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5341                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5342                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5343            }
5344        }
5345
5346        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5347        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5348            self.write_space();
5349            match &join.this {
5350                Expression::Tuple(t) => {
5351                    for (i, item) in t.expressions.iter().enumerate() {
5352                        if i > 0 {
5353                            self.write(", ");
5354                        }
5355                        self.generate_expression(item)?;
5356                    }
5357                }
5358                other => {
5359                    self.generate_expression(other)?;
5360                }
5361            }
5362        } else {
5363            self.write_space();
5364            self.generate_expression(&join.this)?;
5365        }
5366
5367        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5368        if !join.deferred_condition {
5369            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5370            if let Some(match_cond) = &join.match_condition {
5371                self.write_space();
5372                self.write_keyword("MATCH_CONDITION");
5373                self.write(" (");
5374                self.generate_expression(match_cond)?;
5375                self.write(")");
5376            }
5377
5378            if let Some(on) = &join.on {
5379                if self.config.pretty {
5380                    self.write_newline();
5381                    self.indent_level += 1;
5382                    self.write_indent();
5383                    self.write_keyword("ON");
5384                    self.write_space();
5385                    self.generate_join_on_condition(on)?;
5386                    self.indent_level -= 1;
5387                } else {
5388                    self.write_space();
5389                    self.write_keyword("ON");
5390                    self.write_space();
5391                    self.generate_expression(on)?;
5392                }
5393            }
5394
5395            if !join.using.is_empty() {
5396                if self.config.pretty {
5397                    self.write_newline();
5398                    self.indent_level += 1;
5399                    self.write_indent();
5400                    self.write_keyword("USING");
5401                    self.write(" (");
5402                    for (i, col) in join.using.iter().enumerate() {
5403                        if i > 0 {
5404                            self.write(", ");
5405                        }
5406                        self.generate_identifier(col)?;
5407                    }
5408                    self.write(")");
5409                    self.indent_level -= 1;
5410                } else {
5411                    self.write_space();
5412                    self.write_keyword("USING");
5413                    self.write(" (");
5414                    for (i, col) in join.using.iter().enumerate() {
5415                        if i > 0 {
5416                            self.write(", ");
5417                        }
5418                        self.generate_identifier(col)?;
5419                    }
5420                    self.write(")");
5421                }
5422            }
5423        }
5424
5425        // Generate PIVOT/UNPIVOT expressions that follow this join
5426        for pivot in &join.pivots {
5427            self.write_space();
5428            self.generate_expression(pivot)?;
5429        }
5430
5431        Ok(())
5432    }
5433
5434    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5435    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5436        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5437        if let Some(match_cond) = &join.match_condition {
5438            self.write_space();
5439            self.write_keyword("MATCH_CONDITION");
5440            self.write(" (");
5441            self.generate_expression(match_cond)?;
5442            self.write(")");
5443        }
5444
5445        if let Some(on) = &join.on {
5446            if self.config.pretty {
5447                self.write_newline();
5448                self.indent_level += 1;
5449                self.write_indent();
5450                self.write_keyword("ON");
5451                self.write_space();
5452                // In pretty mode, split AND conditions onto separate lines
5453                self.generate_join_on_condition(on)?;
5454                self.indent_level -= 1;
5455            } else {
5456                self.write_space();
5457                self.write_keyword("ON");
5458                self.write_space();
5459                self.generate_expression(on)?;
5460            }
5461        }
5462
5463        if !join.using.is_empty() {
5464            if self.config.pretty {
5465                self.write_newline();
5466                self.indent_level += 1;
5467                self.write_indent();
5468                self.write_keyword("USING");
5469                self.write(" (");
5470                for (i, col) in join.using.iter().enumerate() {
5471                    if i > 0 {
5472                        self.write(", ");
5473                    }
5474                    self.generate_identifier(col)?;
5475                }
5476                self.write(")");
5477                self.indent_level -= 1;
5478            } else {
5479                self.write_space();
5480                self.write_keyword("USING");
5481                self.write(" (");
5482                for (i, col) in join.using.iter().enumerate() {
5483                    if i > 0 {
5484                        self.write(", ");
5485                    }
5486                    self.generate_identifier(col)?;
5487                }
5488                self.write(")");
5489            }
5490        }
5491
5492        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5493        for pivot in &join.pivots {
5494            self.write_space();
5495            self.generate_expression(pivot)?;
5496        }
5497
5498        Ok(())
5499    }
5500
5501    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5502    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5503        if let Expression::And(and_op) = expr {
5504            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5505                self.generate_expression(conditions[0])?;
5506                for condition in conditions.iter().skip(1) {
5507                    self.write_newline();
5508                    self.write_indent();
5509                    self.write_keyword("AND");
5510                    self.write_space();
5511                    self.generate_expression(condition)?;
5512                }
5513                return Ok(());
5514            }
5515        }
5516
5517        self.generate_expression(expr)
5518    }
5519
5520    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5521        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5522        self.write("(");
5523        self.generate_expression(&jt.left)?;
5524
5525        // Generate all joins
5526        for join in &jt.joins {
5527            self.generate_join(join)?;
5528        }
5529
5530        // Generate LATERAL VIEW clauses (Hive/Spark)
5531        for lv in &jt.lateral_views {
5532            self.generate_lateral_view(lv)?;
5533        }
5534
5535        self.write(")");
5536
5537        // Alias
5538        if let Some(alias) = &jt.alias {
5539            self.write_space();
5540            self.write_keyword("AS");
5541            self.write_space();
5542            self.generate_identifier(alias)?;
5543        }
5544
5545        Ok(())
5546    }
5547
5548    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5549        use crate::dialects::DialectType;
5550
5551        if self.config.pretty {
5552            self.write_newline();
5553            self.write_indent();
5554        } else {
5555            self.write_space();
5556        }
5557
5558        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5559        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5560        let use_lateral_join = matches!(
5561            self.config.dialect,
5562            Some(DialectType::PostgreSQL)
5563                | Some(DialectType::DuckDB)
5564                | Some(DialectType::Snowflake)
5565                | Some(DialectType::TSQL)
5566                | Some(DialectType::Presto)
5567                | Some(DialectType::Trino)
5568                | Some(DialectType::Athena)
5569        );
5570
5571        // Check if target dialect should use UNNEST instead of EXPLODE
5572        let use_unnest = matches!(
5573            self.config.dialect,
5574            Some(DialectType::DuckDB)
5575                | Some(DialectType::Presto)
5576                | Some(DialectType::Trino)
5577                | Some(DialectType::Athena)
5578        );
5579
5580        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5581        let (is_posexplode, func_args) = match &lv.this {
5582            Expression::Explode(uf) => {
5583                // Expression::Explode is the dedicated EXPLODE expression type
5584                (false, vec![uf.this.clone()])
5585            }
5586            Expression::Unnest(uf) => {
5587                let mut args = vec![uf.this.clone()];
5588                args.extend(uf.expressions.clone());
5589                (false, args)
5590            }
5591            Expression::Function(func) => {
5592                let name = func.name.to_uppercase();
5593                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5594                    (true, func.args.clone())
5595                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5596                    (false, func.args.clone())
5597                } else {
5598                    (false, vec![])
5599                }
5600            }
5601            _ => (false, vec![]),
5602        };
5603
5604        if use_lateral_join {
5605            // Convert to CROSS JOIN for PostgreSQL-like dialects
5606            if lv.outer {
5607                self.write_keyword("LEFT JOIN LATERAL");
5608            } else {
5609                self.write_keyword("CROSS JOIN");
5610            }
5611            self.write_space();
5612
5613            if use_unnest && !func_args.is_empty() {
5614                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5615                // For DuckDB, also convert ARRAY(y) -> [y]
5616                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5617                    // DuckDB: ARRAY(y) -> [y]
5618                    func_args
5619                        .iter()
5620                        .map(|a| {
5621                            if let Expression::Function(ref f) = a {
5622                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5623                                    return Expression::ArrayFunc(Box::new(
5624                                        crate::expressions::ArrayConstructor {
5625                                            expressions: f.args.clone(),
5626                                            bracket_notation: true,
5627                                            use_list_keyword: false,
5628                                        },
5629                                    ));
5630                                }
5631                            }
5632                            a.clone()
5633                        })
5634                        .collect::<Vec<_>>()
5635                } else if matches!(
5636                    self.config.dialect,
5637                    Some(DialectType::Presto)
5638                        | Some(DialectType::Trino)
5639                        | Some(DialectType::Athena)
5640                ) {
5641                    // Presto: ARRAY(y) -> ARRAY[y]
5642                    func_args
5643                        .iter()
5644                        .map(|a| {
5645                            if let Expression::Function(ref f) = a {
5646                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5647                                    return Expression::ArrayFunc(Box::new(
5648                                        crate::expressions::ArrayConstructor {
5649                                            expressions: f.args.clone(),
5650                                            bracket_notation: true,
5651                                            use_list_keyword: false,
5652                                        },
5653                                    ));
5654                                }
5655                            }
5656                            a.clone()
5657                        })
5658                        .collect::<Vec<_>>()
5659                } else {
5660                    func_args
5661                };
5662
5663                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5664                if is_posexplode {
5665                    self.write_keyword("LATERAL");
5666                    self.write(" (");
5667                    self.write_keyword("SELECT");
5668                    self.write_space();
5669
5670                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5671                    // column_aliases[0] is the position column, rest are data columns
5672                    let pos_alias = if !lv.column_aliases.is_empty() {
5673                        lv.column_aliases[0].clone()
5674                    } else {
5675                        Identifier::new("pos")
5676                    };
5677                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5678                        lv.column_aliases[1..].to_vec()
5679                    } else {
5680                        vec![Identifier::new("col")]
5681                    };
5682
5683                    // pos - 1 AS pos
5684                    self.generate_identifier(&pos_alias)?;
5685                    self.write(" - 1");
5686                    self.write_space();
5687                    self.write_keyword("AS");
5688                    self.write_space();
5689                    self.generate_identifier(&pos_alias)?;
5690
5691                    // , col [, key, value ...]
5692                    for data_col in &data_aliases {
5693                        self.write(", ");
5694                        self.generate_identifier(data_col)?;
5695                    }
5696
5697                    self.write_space();
5698                    self.write_keyword("FROM");
5699                    self.write_space();
5700                    self.write_keyword("UNNEST");
5701                    self.write("(");
5702                    for (i, arg) in unnest_args.iter().enumerate() {
5703                        if i > 0 {
5704                            self.write(", ");
5705                        }
5706                        self.generate_expression(arg)?;
5707                    }
5708                    self.write(")");
5709                    self.write_space();
5710                    self.write_keyword("WITH ORDINALITY");
5711                    self.write_space();
5712                    self.write_keyword("AS");
5713                    self.write_space();
5714
5715                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5716                    let table_alias_ident = lv
5717                        .table_alias
5718                        .clone()
5719                        .unwrap_or_else(|| Identifier::new("t"));
5720                    self.generate_identifier(&table_alias_ident)?;
5721                    self.write("(");
5722                    for (i, data_col) in data_aliases.iter().enumerate() {
5723                        if i > 0 {
5724                            self.write(", ");
5725                        }
5726                        self.generate_identifier(data_col)?;
5727                    }
5728                    self.write(", ");
5729                    self.generate_identifier(&pos_alias)?;
5730                    self.write("))");
5731                } else {
5732                    self.write_keyword("UNNEST");
5733                    self.write("(");
5734                    for (i, arg) in unnest_args.iter().enumerate() {
5735                        if i > 0 {
5736                            self.write(", ");
5737                        }
5738                        self.generate_expression(arg)?;
5739                    }
5740                    self.write(")");
5741
5742                    // Add table and column aliases for non-POSEXPLODE
5743                    if let Some(alias) = &lv.table_alias {
5744                        self.write_space();
5745                        self.write_keyword("AS");
5746                        self.write_space();
5747                        self.generate_identifier(alias)?;
5748                        if !lv.column_aliases.is_empty() {
5749                            self.write("(");
5750                            for (i, col) in lv.column_aliases.iter().enumerate() {
5751                                if i > 0 {
5752                                    self.write(", ");
5753                                }
5754                                self.generate_identifier(col)?;
5755                            }
5756                            self.write(")");
5757                        }
5758                    } else if !lv.column_aliases.is_empty() {
5759                        self.write_space();
5760                        self.write_keyword("AS");
5761                        self.write(" t(");
5762                        for (i, col) in lv.column_aliases.iter().enumerate() {
5763                            if i > 0 {
5764                                self.write(", ");
5765                            }
5766                            self.generate_identifier(col)?;
5767                        }
5768                        self.write(")");
5769                    }
5770                }
5771            } else {
5772                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5773                if !lv.outer {
5774                    self.write_keyword("LATERAL");
5775                    self.write_space();
5776                }
5777                self.generate_expression(&lv.this)?;
5778
5779                // Add table and column aliases
5780                if let Some(alias) = &lv.table_alias {
5781                    self.write_space();
5782                    self.write_keyword("AS");
5783                    self.write_space();
5784                    self.generate_identifier(alias)?;
5785                    if !lv.column_aliases.is_empty() {
5786                        self.write("(");
5787                        for (i, col) in lv.column_aliases.iter().enumerate() {
5788                            if i > 0 {
5789                                self.write(", ");
5790                            }
5791                            self.generate_identifier(col)?;
5792                        }
5793                        self.write(")");
5794                    }
5795                } else if !lv.column_aliases.is_empty() {
5796                    self.write_space();
5797                    self.write_keyword("AS");
5798                    self.write(" t(");
5799                    for (i, col) in lv.column_aliases.iter().enumerate() {
5800                        if i > 0 {
5801                            self.write(", ");
5802                        }
5803                        self.generate_identifier(col)?;
5804                    }
5805                    self.write(")");
5806                }
5807            }
5808
5809            // For LEFT JOIN LATERAL, need ON TRUE
5810            if lv.outer {
5811                self.write_space();
5812                self.write_keyword("ON TRUE");
5813            }
5814        } else {
5815            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5816            self.write_keyword("LATERAL VIEW");
5817            if lv.outer {
5818                self.write_space();
5819                self.write_keyword("OUTER");
5820            }
5821            if self.config.pretty {
5822                self.write_newline();
5823                self.write_indent();
5824            } else {
5825                self.write_space();
5826            }
5827            self.generate_expression(&lv.this)?;
5828
5829            // Table alias
5830            if let Some(alias) = &lv.table_alias {
5831                self.write_space();
5832                self.generate_identifier(alias)?;
5833            }
5834
5835            // Column aliases
5836            if !lv.column_aliases.is_empty() {
5837                self.write_space();
5838                self.write_keyword("AS");
5839                self.write_space();
5840                for (i, col) in lv.column_aliases.iter().enumerate() {
5841                    if i > 0 {
5842                        self.write(", ");
5843                    }
5844                    self.generate_identifier(col)?;
5845                }
5846            }
5847        }
5848
5849        Ok(())
5850    }
5851
5852    fn generate_union(&mut self, union: &Union) -> Result<()> {
5853        // WITH clause
5854        if let Some(with) = &union.with {
5855            self.generate_with(with)?;
5856            self.write_space();
5857        }
5858        self.generate_expression(&union.left)?;
5859        if self.config.pretty {
5860            self.write_newline();
5861            self.write_indent();
5862        } else {
5863            self.write_space();
5864        }
5865
5866        // BigQuery set operation modifiers: [side] [kind] UNION
5867        if let Some(side) = &union.side {
5868            self.write_keyword(side);
5869            self.write_space();
5870        }
5871        if let Some(kind) = &union.kind {
5872            self.write_keyword(kind);
5873            self.write_space();
5874        }
5875
5876        self.write_keyword("UNION");
5877        if union.all {
5878            self.write_space();
5879            self.write_keyword("ALL");
5880        } else if union.distinct {
5881            self.write_space();
5882            self.write_keyword("DISTINCT");
5883        }
5884
5885        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5886        // DuckDB: BY NAME
5887        if union.corresponding || union.by_name {
5888            self.write_space();
5889            self.write_keyword("BY NAME");
5890        }
5891        if !union.on_columns.is_empty() {
5892            self.write_space();
5893            self.write_keyword("ON");
5894            self.write(" (");
5895            for (i, col) in union.on_columns.iter().enumerate() {
5896                if i > 0 {
5897                    self.write(", ");
5898                }
5899                self.generate_expression(col)?;
5900            }
5901            self.write(")");
5902        }
5903
5904        if self.config.pretty {
5905            self.write_newline();
5906            self.write_indent();
5907        } else {
5908            self.write_space();
5909        }
5910        self.generate_expression(&union.right)?;
5911        // ORDER BY, LIMIT, OFFSET for the set operation
5912        if let Some(order_by) = &union.order_by {
5913            if self.config.pretty {
5914                self.write_newline();
5915            } else {
5916                self.write_space();
5917            }
5918            self.write_keyword("ORDER BY");
5919            self.write_space();
5920            for (i, ordered) in order_by.expressions.iter().enumerate() {
5921                if i > 0 {
5922                    self.write(", ");
5923                }
5924                self.generate_ordered(ordered)?;
5925            }
5926        }
5927        if let Some(limit) = &union.limit {
5928            if self.config.pretty {
5929                self.write_newline();
5930            } else {
5931                self.write_space();
5932            }
5933            self.write_keyword("LIMIT");
5934            self.write_space();
5935            self.generate_expression(limit)?;
5936        }
5937        if let Some(offset) = &union.offset {
5938            if self.config.pretty {
5939                self.write_newline();
5940            } else {
5941                self.write_space();
5942            }
5943            self.write_keyword("OFFSET");
5944            self.write_space();
5945            self.generate_expression(offset)?;
5946        }
5947        // DISTRIBUTE BY (Hive/Spark)
5948        if let Some(distribute_by) = &union.distribute_by {
5949            self.write_space();
5950            self.write_keyword("DISTRIBUTE BY");
5951            self.write_space();
5952            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5953                if i > 0 {
5954                    self.write(", ");
5955                }
5956                self.generate_expression(expr)?;
5957            }
5958        }
5959        // SORT BY (Hive/Spark)
5960        if let Some(sort_by) = &union.sort_by {
5961            self.write_space();
5962            self.write_keyword("SORT BY");
5963            self.write_space();
5964            for (i, ord) in sort_by.expressions.iter().enumerate() {
5965                if i > 0 {
5966                    self.write(", ");
5967                }
5968                self.generate_ordered(ord)?;
5969            }
5970        }
5971        // CLUSTER BY (Hive/Spark)
5972        if let Some(cluster_by) = &union.cluster_by {
5973            self.write_space();
5974            self.write_keyword("CLUSTER BY");
5975            self.write_space();
5976            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5977                if i > 0 {
5978                    self.write(", ");
5979                }
5980                self.generate_ordered(ord)?;
5981            }
5982        }
5983        Ok(())
5984    }
5985
5986    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5987        // WITH clause
5988        if let Some(with) = &intersect.with {
5989            self.generate_with(with)?;
5990            self.write_space();
5991        }
5992        self.generate_expression(&intersect.left)?;
5993        if self.config.pretty {
5994            self.write_newline();
5995            self.write_indent();
5996        } else {
5997            self.write_space();
5998        }
5999
6000        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6001        if let Some(side) = &intersect.side {
6002            self.write_keyword(side);
6003            self.write_space();
6004        }
6005        if let Some(kind) = &intersect.kind {
6006            self.write_keyword(kind);
6007            self.write_space();
6008        }
6009
6010        self.write_keyword("INTERSECT");
6011        if intersect.all {
6012            self.write_space();
6013            self.write_keyword("ALL");
6014        } else if intersect.distinct {
6015            self.write_space();
6016            self.write_keyword("DISTINCT");
6017        }
6018
6019        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6020        // DuckDB: BY NAME
6021        if intersect.corresponding || intersect.by_name {
6022            self.write_space();
6023            self.write_keyword("BY NAME");
6024        }
6025        if !intersect.on_columns.is_empty() {
6026            self.write_space();
6027            self.write_keyword("ON");
6028            self.write(" (");
6029            for (i, col) in intersect.on_columns.iter().enumerate() {
6030                if i > 0 {
6031                    self.write(", ");
6032                }
6033                self.generate_expression(col)?;
6034            }
6035            self.write(")");
6036        }
6037
6038        if self.config.pretty {
6039            self.write_newline();
6040            self.write_indent();
6041        } else {
6042            self.write_space();
6043        }
6044        self.generate_expression(&intersect.right)?;
6045        // ORDER BY, LIMIT, OFFSET for the set operation
6046        if let Some(order_by) = &intersect.order_by {
6047            if self.config.pretty {
6048                self.write_newline();
6049            } else {
6050                self.write_space();
6051            }
6052            self.write_keyword("ORDER BY");
6053            self.write_space();
6054            for (i, ordered) in order_by.expressions.iter().enumerate() {
6055                if i > 0 {
6056                    self.write(", ");
6057                }
6058                self.generate_ordered(ordered)?;
6059            }
6060        }
6061        if let Some(limit) = &intersect.limit {
6062            if self.config.pretty {
6063                self.write_newline();
6064            } else {
6065                self.write_space();
6066            }
6067            self.write_keyword("LIMIT");
6068            self.write_space();
6069            self.generate_expression(limit)?;
6070        }
6071        if let Some(offset) = &intersect.offset {
6072            if self.config.pretty {
6073                self.write_newline();
6074            } else {
6075                self.write_space();
6076            }
6077            self.write_keyword("OFFSET");
6078            self.write_space();
6079            self.generate_expression(offset)?;
6080        }
6081        // DISTRIBUTE BY (Hive/Spark)
6082        if let Some(distribute_by) = &intersect.distribute_by {
6083            self.write_space();
6084            self.write_keyword("DISTRIBUTE BY");
6085            self.write_space();
6086            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6087                if i > 0 {
6088                    self.write(", ");
6089                }
6090                self.generate_expression(expr)?;
6091            }
6092        }
6093        // SORT BY (Hive/Spark)
6094        if let Some(sort_by) = &intersect.sort_by {
6095            self.write_space();
6096            self.write_keyword("SORT BY");
6097            self.write_space();
6098            for (i, ord) in sort_by.expressions.iter().enumerate() {
6099                if i > 0 {
6100                    self.write(", ");
6101                }
6102                self.generate_ordered(ord)?;
6103            }
6104        }
6105        // CLUSTER BY (Hive/Spark)
6106        if let Some(cluster_by) = &intersect.cluster_by {
6107            self.write_space();
6108            self.write_keyword("CLUSTER BY");
6109            self.write_space();
6110            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6111                if i > 0 {
6112                    self.write(", ");
6113                }
6114                self.generate_ordered(ord)?;
6115            }
6116        }
6117        Ok(())
6118    }
6119
6120    fn generate_except(&mut self, except: &Except) -> Result<()> {
6121        use crate::dialects::DialectType;
6122
6123        // WITH clause
6124        if let Some(with) = &except.with {
6125            self.generate_with(with)?;
6126            self.write_space();
6127        }
6128
6129        self.generate_expression(&except.left)?;
6130        if self.config.pretty {
6131            self.write_newline();
6132            self.write_indent();
6133        } else {
6134            self.write_space();
6135        }
6136
6137        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6138        if let Some(side) = &except.side {
6139            self.write_keyword(side);
6140            self.write_space();
6141        }
6142        if let Some(kind) = &except.kind {
6143            self.write_keyword(kind);
6144            self.write_space();
6145        }
6146
6147        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6148        match self.config.dialect {
6149            Some(DialectType::Oracle) if !except.all => {
6150                self.write_keyword("MINUS");
6151            }
6152            Some(DialectType::ClickHouse) => {
6153                // ClickHouse: drop ALL from EXCEPT ALL
6154                self.write_keyword("EXCEPT");
6155                if except.distinct {
6156                    self.write_space();
6157                    self.write_keyword("DISTINCT");
6158                }
6159            }
6160            Some(DialectType::BigQuery) => {
6161                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6162                self.write_keyword("EXCEPT");
6163                if except.all {
6164                    self.write_space();
6165                    self.write_keyword("ALL");
6166                } else {
6167                    self.write_space();
6168                    self.write_keyword("DISTINCT");
6169                }
6170            }
6171            _ => {
6172                self.write_keyword("EXCEPT");
6173                if except.all {
6174                    self.write_space();
6175                    self.write_keyword("ALL");
6176                } else if except.distinct {
6177                    self.write_space();
6178                    self.write_keyword("DISTINCT");
6179                }
6180            }
6181        }
6182
6183        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6184        // DuckDB: BY NAME
6185        if except.corresponding || except.by_name {
6186            self.write_space();
6187            self.write_keyword("BY NAME");
6188        }
6189        if !except.on_columns.is_empty() {
6190            self.write_space();
6191            self.write_keyword("ON");
6192            self.write(" (");
6193            for (i, col) in except.on_columns.iter().enumerate() {
6194                if i > 0 {
6195                    self.write(", ");
6196                }
6197                self.generate_expression(col)?;
6198            }
6199            self.write(")");
6200        }
6201
6202        if self.config.pretty {
6203            self.write_newline();
6204            self.write_indent();
6205        } else {
6206            self.write_space();
6207        }
6208        self.generate_expression(&except.right)?;
6209        // ORDER BY, LIMIT, OFFSET for the set operation
6210        if let Some(order_by) = &except.order_by {
6211            if self.config.pretty {
6212                self.write_newline();
6213            } else {
6214                self.write_space();
6215            }
6216            self.write_keyword("ORDER BY");
6217            self.write_space();
6218            for (i, ordered) in order_by.expressions.iter().enumerate() {
6219                if i > 0 {
6220                    self.write(", ");
6221                }
6222                self.generate_ordered(ordered)?;
6223            }
6224        }
6225        if let Some(limit) = &except.limit {
6226            if self.config.pretty {
6227                self.write_newline();
6228            } else {
6229                self.write_space();
6230            }
6231            self.write_keyword("LIMIT");
6232            self.write_space();
6233            self.generate_expression(limit)?;
6234        }
6235        if let Some(offset) = &except.offset {
6236            if self.config.pretty {
6237                self.write_newline();
6238            } else {
6239                self.write_space();
6240            }
6241            self.write_keyword("OFFSET");
6242            self.write_space();
6243            self.generate_expression(offset)?;
6244        }
6245        // DISTRIBUTE BY (Hive/Spark)
6246        if let Some(distribute_by) = &except.distribute_by {
6247            self.write_space();
6248            self.write_keyword("DISTRIBUTE BY");
6249            self.write_space();
6250            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6251                if i > 0 {
6252                    self.write(", ");
6253                }
6254                self.generate_expression(expr)?;
6255            }
6256        }
6257        // SORT BY (Hive/Spark)
6258        if let Some(sort_by) = &except.sort_by {
6259            self.write_space();
6260            self.write_keyword("SORT BY");
6261            self.write_space();
6262            for (i, ord) in sort_by.expressions.iter().enumerate() {
6263                if i > 0 {
6264                    self.write(", ");
6265                }
6266                self.generate_ordered(ord)?;
6267            }
6268        }
6269        // CLUSTER BY (Hive/Spark)
6270        if let Some(cluster_by) = &except.cluster_by {
6271            self.write_space();
6272            self.write_keyword("CLUSTER BY");
6273            self.write_space();
6274            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6275                if i > 0 {
6276                    self.write(", ");
6277                }
6278                self.generate_ordered(ord)?;
6279            }
6280        }
6281        Ok(())
6282    }
6283
6284    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6285        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6286        let prepend_query_cte = if insert.with.is_none() {
6287            use crate::dialects::DialectType;
6288            let should_prepend = matches!(
6289                self.config.dialect,
6290                Some(DialectType::TSQL)
6291                    | Some(DialectType::Fabric)
6292                    | Some(DialectType::Spark)
6293                    | Some(DialectType::Databricks)
6294                    | Some(DialectType::Hive)
6295            );
6296            if should_prepend {
6297                if let Some(Expression::Select(select)) = &insert.query {
6298                    select.with.clone()
6299                } else {
6300                    None
6301                }
6302            } else {
6303                None
6304            }
6305        } else {
6306            None
6307        };
6308
6309        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6310        if let Some(with) = &insert.with {
6311            self.generate_with(with)?;
6312            self.write_space();
6313        } else if let Some(with) = &prepend_query_cte {
6314            self.generate_with(with)?;
6315            self.write_space();
6316        }
6317
6318        // Output leading comments before INSERT
6319        for comment in &insert.leading_comments {
6320            self.write_formatted_comment(comment);
6321            self.write(" ");
6322        }
6323
6324        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6325        if let Some(dir) = &insert.directory {
6326            self.write_keyword("INSERT OVERWRITE");
6327            if dir.local {
6328                self.write_space();
6329                self.write_keyword("LOCAL");
6330            }
6331            self.write_space();
6332            self.write_keyword("DIRECTORY");
6333            self.write_space();
6334            self.write("'");
6335            self.write(&dir.path);
6336            self.write("'");
6337
6338            // ROW FORMAT clause
6339            if let Some(row_format) = &dir.row_format {
6340                self.write_space();
6341                self.write_keyword("ROW FORMAT");
6342                if row_format.delimited {
6343                    self.write_space();
6344                    self.write_keyword("DELIMITED");
6345                }
6346                if let Some(val) = &row_format.fields_terminated_by {
6347                    self.write_space();
6348                    self.write_keyword("FIELDS TERMINATED BY");
6349                    self.write_space();
6350                    self.write("'");
6351                    self.write(val);
6352                    self.write("'");
6353                }
6354                if let Some(val) = &row_format.collection_items_terminated_by {
6355                    self.write_space();
6356                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6357                    self.write_space();
6358                    self.write("'");
6359                    self.write(val);
6360                    self.write("'");
6361                }
6362                if let Some(val) = &row_format.map_keys_terminated_by {
6363                    self.write_space();
6364                    self.write_keyword("MAP KEYS TERMINATED BY");
6365                    self.write_space();
6366                    self.write("'");
6367                    self.write(val);
6368                    self.write("'");
6369                }
6370                if let Some(val) = &row_format.lines_terminated_by {
6371                    self.write_space();
6372                    self.write_keyword("LINES TERMINATED BY");
6373                    self.write_space();
6374                    self.write("'");
6375                    self.write(val);
6376                    self.write("'");
6377                }
6378                if let Some(val) = &row_format.null_defined_as {
6379                    self.write_space();
6380                    self.write_keyword("NULL DEFINED AS");
6381                    self.write_space();
6382                    self.write("'");
6383                    self.write(val);
6384                    self.write("'");
6385                }
6386            }
6387
6388            // STORED AS clause
6389            if let Some(format) = &dir.stored_as {
6390                self.write_space();
6391                self.write_keyword("STORED AS");
6392                self.write_space();
6393                self.write_keyword(format);
6394            }
6395
6396            // Query (SELECT statement)
6397            if let Some(query) = &insert.query {
6398                self.write_space();
6399                self.generate_expression(query)?;
6400            }
6401
6402            return Ok(());
6403        }
6404
6405        if insert.is_replace {
6406            // MySQL/SQLite REPLACE INTO statement
6407            self.write_keyword("REPLACE INTO");
6408        } else if insert.overwrite {
6409            // Use dialect-specific INSERT OVERWRITE format
6410            self.write_keyword("INSERT");
6411            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6412            if let Some(ref hint) = insert.hint {
6413                self.generate_hint(hint)?;
6414            }
6415            self.write(&self.config.insert_overwrite.to_uppercase());
6416        } else if let Some(ref action) = insert.conflict_action {
6417            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6418            self.write_keyword("INSERT OR");
6419            self.write_space();
6420            self.write_keyword(action);
6421            self.write_space();
6422            self.write_keyword("INTO");
6423        } else if insert.ignore {
6424            // MySQL INSERT IGNORE syntax
6425            self.write_keyword("INSERT IGNORE INTO");
6426        } else {
6427            self.write_keyword("INSERT");
6428            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6429            if let Some(ref hint) = insert.hint {
6430                self.generate_hint(hint)?;
6431            }
6432            self.write_space();
6433            self.write_keyword("INTO");
6434        }
6435        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6436        if let Some(ref func) = insert.function_target {
6437            self.write_space();
6438            self.write_keyword("FUNCTION");
6439            self.write_space();
6440            self.generate_expression(func)?;
6441        } else {
6442            self.write_space();
6443            self.generate_table(&insert.table)?;
6444        }
6445
6446        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6447        if let Some(ref alias) = insert.alias {
6448            self.write_space();
6449            if insert.alias_explicit_as {
6450                self.write_keyword("AS");
6451                self.write_space();
6452            }
6453            self.generate_identifier(alias)?;
6454        }
6455
6456        // IF EXISTS clause (Hive)
6457        if insert.if_exists {
6458            self.write_space();
6459            self.write_keyword("IF EXISTS");
6460        }
6461
6462        // REPLACE WHERE clause (Databricks)
6463        if let Some(ref replace_where) = insert.replace_where {
6464            if self.config.pretty {
6465                self.write_newline();
6466                self.write_indent();
6467            } else {
6468                self.write_space();
6469            }
6470            self.write_keyword("REPLACE WHERE");
6471            self.write_space();
6472            self.generate_expression(replace_where)?;
6473        }
6474
6475        // Generate PARTITION clause if present
6476        if !insert.partition.is_empty() {
6477            self.write_space();
6478            self.write_keyword("PARTITION");
6479            self.write("(");
6480            for (i, (col, val)) in insert.partition.iter().enumerate() {
6481                if i > 0 {
6482                    self.write(", ");
6483                }
6484                self.generate_identifier(col)?;
6485                if let Some(v) = val {
6486                    self.write(" = ");
6487                    self.generate_expression(v)?;
6488                }
6489            }
6490            self.write(")");
6491        }
6492
6493        // ClickHouse: PARTITION BY expr
6494        if let Some(ref partition_by) = insert.partition_by {
6495            self.write_space();
6496            self.write_keyword("PARTITION BY");
6497            self.write_space();
6498            self.generate_expression(partition_by)?;
6499        }
6500
6501        // ClickHouse: SETTINGS key = val, ...
6502        if !insert.settings.is_empty() {
6503            self.write_space();
6504            self.write_keyword("SETTINGS");
6505            self.write_space();
6506            for (i, setting) in insert.settings.iter().enumerate() {
6507                if i > 0 {
6508                    self.write(", ");
6509                }
6510                self.generate_expression(setting)?;
6511            }
6512        }
6513
6514        if !insert.columns.is_empty() {
6515            if insert.alias.is_some() && insert.alias_explicit_as {
6516                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6517                self.write("(");
6518            } else {
6519                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6520                self.write(" (");
6521            }
6522            for (i, col) in insert.columns.iter().enumerate() {
6523                if i > 0 {
6524                    self.write(", ");
6525                }
6526                self.generate_identifier(col)?;
6527            }
6528            self.write(")");
6529        }
6530
6531        // OUTPUT clause (TSQL)
6532        if let Some(ref output) = insert.output {
6533            self.generate_output_clause(output)?;
6534        }
6535
6536        // BY NAME modifier (DuckDB)
6537        if insert.by_name {
6538            self.write_space();
6539            self.write_keyword("BY NAME");
6540        }
6541
6542        if insert.default_values {
6543            self.write_space();
6544            self.write_keyword("DEFAULT VALUES");
6545        } else if let Some(query) = &insert.query {
6546            if self.config.pretty {
6547                self.write_newline();
6548            } else {
6549                self.write_space();
6550            }
6551            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6552            if prepend_query_cte.is_some() {
6553                if let Expression::Select(select) = query {
6554                    let mut select_no_with = select.clone();
6555                    select_no_with.with = None;
6556                    self.generate_select(&select_no_with)?;
6557                } else {
6558                    self.generate_expression(query)?;
6559                }
6560            } else {
6561                self.generate_expression(query)?;
6562            }
6563        } else if !insert.values.is_empty() {
6564            if self.config.pretty {
6565                // Pretty printing: VALUES on new line, each tuple indented
6566                self.write_newline();
6567                self.write_keyword("VALUES");
6568                self.write_newline();
6569                self.indent_level += 1;
6570                for (i, row) in insert.values.iter().enumerate() {
6571                    if i > 0 {
6572                        self.write(",");
6573                        self.write_newline();
6574                    }
6575                    self.write_indent();
6576                    self.write("(");
6577                    for (j, val) in row.iter().enumerate() {
6578                        if j > 0 {
6579                            self.write(", ");
6580                        }
6581                        self.generate_expression(val)?;
6582                    }
6583                    self.write(")");
6584                }
6585                self.indent_level -= 1;
6586            } else {
6587                // Non-pretty: single line
6588                self.write_space();
6589                self.write_keyword("VALUES");
6590                for (i, row) in insert.values.iter().enumerate() {
6591                    if i > 0 {
6592                        self.write(",");
6593                    }
6594                    self.write(" (");
6595                    for (j, val) in row.iter().enumerate() {
6596                        if j > 0 {
6597                            self.write(", ");
6598                        }
6599                        self.generate_expression(val)?;
6600                    }
6601                    self.write(")");
6602                }
6603            }
6604        }
6605
6606        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6607        if let Some(ref source) = insert.source {
6608            self.write_space();
6609            self.write_keyword("TABLE");
6610            self.write_space();
6611            self.generate_expression(source)?;
6612        }
6613
6614        // Source alias (MySQL: VALUES (...) AS new_data)
6615        if let Some(alias) = &insert.source_alias {
6616            self.write_space();
6617            self.write_keyword("AS");
6618            self.write_space();
6619            self.generate_identifier(alias)?;
6620        }
6621
6622        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6623        if let Some(on_conflict) = &insert.on_conflict {
6624            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6625                self.write_space();
6626                self.generate_expression(on_conflict)?;
6627            }
6628        }
6629
6630        // RETURNING clause
6631        if !insert.returning.is_empty() {
6632            self.write_space();
6633            self.write_keyword("RETURNING");
6634            self.write_space();
6635            for (i, expr) in insert.returning.iter().enumerate() {
6636                if i > 0 {
6637                    self.write(", ");
6638                }
6639                self.generate_expression(expr)?;
6640            }
6641        }
6642
6643        Ok(())
6644    }
6645
6646    fn generate_update(&mut self, update: &Update) -> Result<()> {
6647        // Output leading comments before UPDATE
6648        for comment in &update.leading_comments {
6649            self.write_formatted_comment(comment);
6650            self.write(" ");
6651        }
6652
6653        // WITH clause (CTEs)
6654        if let Some(ref with) = update.with {
6655            self.generate_with(with)?;
6656            self.write_space();
6657        }
6658
6659        self.write_keyword("UPDATE");
6660        self.write_space();
6661        self.generate_table(&update.table)?;
6662
6663        let mysql_like_update_from = matches!(
6664            self.config.dialect,
6665            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6666        ) && update.from_clause.is_some();
6667
6668        let mut set_pairs = update.set.clone();
6669
6670        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6671        let mut pre_set_joins = update.table_joins.clone();
6672        if mysql_like_update_from {
6673            let target_name = update
6674                .table
6675                .alias
6676                .as_ref()
6677                .map(|a| a.name.clone())
6678                .unwrap_or_else(|| update.table.name.name.clone());
6679
6680            for (col, _) in &mut set_pairs {
6681                if !col.name.contains('.') {
6682                    col.name = format!("{}.{}", target_name, col.name);
6683                }
6684            }
6685
6686            if let Some(from_clause) = &update.from_clause {
6687                for table_expr in &from_clause.expressions {
6688                    pre_set_joins.push(crate::expressions::Join {
6689                        this: table_expr.clone(),
6690                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6691                            value: true,
6692                        })),
6693                        using: Vec::new(),
6694                        kind: crate::expressions::JoinKind::Inner,
6695                        use_inner_keyword: false,
6696                        use_outer_keyword: false,
6697                        deferred_condition: false,
6698                        join_hint: None,
6699                        match_condition: None,
6700                        pivots: Vec::new(),
6701                        comments: Vec::new(),
6702                        nesting_group: 0,
6703                        directed: false,
6704                    });
6705                }
6706            }
6707            for join in &update.from_joins {
6708                let mut join = join.clone();
6709                if join.on.is_none() && join.using.is_empty() {
6710                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6711                        value: true,
6712                    }));
6713                }
6714                pre_set_joins.push(join);
6715            }
6716        }
6717
6718        // Extra tables for multi-table UPDATE (MySQL syntax)
6719        for extra_table in &update.extra_tables {
6720            self.write(", ");
6721            self.generate_table(extra_table)?;
6722        }
6723
6724        // JOINs attached to the table list (MySQL multi-table syntax)
6725        for join in &pre_set_joins {
6726            // generate_join already adds a leading space
6727            self.generate_join(join)?;
6728        }
6729
6730        // Teradata: FROM clause comes before SET
6731        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6732        if teradata_from_before_set && !mysql_like_update_from {
6733            if let Some(ref from_clause) = update.from_clause {
6734                self.write_space();
6735                self.write_keyword("FROM");
6736                self.write_space();
6737                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6738                    if i > 0 {
6739                        self.write(", ");
6740                    }
6741                    self.generate_expression(table_expr)?;
6742                }
6743            }
6744            for join in &update.from_joins {
6745                self.generate_join(join)?;
6746            }
6747        }
6748
6749        self.write_space();
6750        self.write_keyword("SET");
6751        self.write_space();
6752
6753        for (i, (col, val)) in set_pairs.iter().enumerate() {
6754            if i > 0 {
6755                self.write(", ");
6756            }
6757            self.generate_identifier(col)?;
6758            self.write(" = ");
6759            self.generate_expression(val)?;
6760        }
6761
6762        // OUTPUT clause (TSQL)
6763        if let Some(ref output) = update.output {
6764            self.generate_output_clause(output)?;
6765        }
6766
6767        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6768        if !mysql_like_update_from && !teradata_from_before_set {
6769            if let Some(ref from_clause) = update.from_clause {
6770                self.write_space();
6771                self.write_keyword("FROM");
6772                self.write_space();
6773                // Generate each table in the FROM clause
6774                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6775                    if i > 0 {
6776                        self.write(", ");
6777                    }
6778                    self.generate_expression(table_expr)?;
6779                }
6780            }
6781        }
6782
6783        if !mysql_like_update_from && !teradata_from_before_set {
6784            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6785            for join in &update.from_joins {
6786                self.generate_join(join)?;
6787            }
6788        }
6789
6790        if let Some(where_clause) = &update.where_clause {
6791            self.write_space();
6792            self.write_keyword("WHERE");
6793            self.write_space();
6794            self.generate_expression(&where_clause.this)?;
6795        }
6796
6797        // RETURNING clause
6798        if !update.returning.is_empty() {
6799            self.write_space();
6800            self.write_keyword("RETURNING");
6801            self.write_space();
6802            for (i, expr) in update.returning.iter().enumerate() {
6803                if i > 0 {
6804                    self.write(", ");
6805                }
6806                self.generate_expression(expr)?;
6807            }
6808        }
6809
6810        // ORDER BY clause (MySQL)
6811        if let Some(ref order_by) = update.order_by {
6812            self.write_space();
6813            self.generate_order_by(order_by)?;
6814        }
6815
6816        // LIMIT clause (MySQL)
6817        if let Some(ref limit) = update.limit {
6818            self.write_space();
6819            self.write_keyword("LIMIT");
6820            self.write_space();
6821            self.generate_expression(limit)?;
6822        }
6823
6824        Ok(())
6825    }
6826
6827    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6828        // Output WITH clause if present
6829        if let Some(with) = &delete.with {
6830            self.generate_with(with)?;
6831            self.write_space();
6832        }
6833
6834        // Output leading comments before DELETE
6835        for comment in &delete.leading_comments {
6836            self.write_formatted_comment(comment);
6837            self.write(" ");
6838        }
6839
6840        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6841        if !delete.tables.is_empty() && !delete.tables_from_using {
6842            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6843            self.write_keyword("DELETE");
6844            self.write_space();
6845            for (i, tbl) in delete.tables.iter().enumerate() {
6846                if i > 0 {
6847                    self.write(", ");
6848                }
6849                self.generate_table(tbl)?;
6850            }
6851            // TSQL: OUTPUT clause between target table and FROM
6852            if let Some(ref output) = delete.output {
6853                self.generate_output_clause(output)?;
6854            }
6855            self.write_space();
6856            self.write_keyword("FROM");
6857            self.write_space();
6858            self.generate_table(&delete.table)?;
6859        } else if !delete.tables.is_empty() && delete.tables_from_using {
6860            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6861            self.write_keyword("DELETE FROM");
6862            self.write_space();
6863            for (i, tbl) in delete.tables.iter().enumerate() {
6864                if i > 0 {
6865                    self.write(", ");
6866                }
6867                self.generate_table(tbl)?;
6868            }
6869        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6870            // BigQuery-style DELETE without FROM keyword
6871            self.write_keyword("DELETE");
6872            self.write_space();
6873            self.generate_table(&delete.table)?;
6874        } else {
6875            self.write_keyword("DELETE FROM");
6876            self.write_space();
6877            self.generate_table(&delete.table)?;
6878        }
6879
6880        // ClickHouse: ON CLUSTER clause
6881        if let Some(ref on_cluster) = delete.on_cluster {
6882            self.write_space();
6883            self.generate_on_cluster(on_cluster)?;
6884        }
6885
6886        // FORCE INDEX hint (MySQL)
6887        if let Some(ref idx) = delete.force_index {
6888            self.write_space();
6889            self.write_keyword("FORCE INDEX");
6890            self.write(" (");
6891            self.write(idx);
6892            self.write(")");
6893        }
6894
6895        // Optional alias
6896        if let Some(ref alias) = delete.alias {
6897            self.write_space();
6898            if delete.alias_explicit_as
6899                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6900            {
6901                self.write_keyword("AS");
6902                self.write_space();
6903            }
6904            self.generate_identifier(alias)?;
6905        }
6906
6907        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6908        if !delete.tables_from_using {
6909            for join in &delete.joins {
6910                self.generate_join(join)?;
6911            }
6912        }
6913
6914        // USING clause (PostgreSQL/DuckDB/MySQL)
6915        if !delete.using.is_empty() {
6916            self.write_space();
6917            self.write_keyword("USING");
6918            for (i, table) in delete.using.iter().enumerate() {
6919                if i > 0 {
6920                    self.write(",");
6921                }
6922                self.write_space();
6923                // Check if the table has subquery hints (DuckDB USING with subquery)
6924                if !table.hints.is_empty() && table.name.is_empty() {
6925                    // Subquery in USING: (VALUES ...) AS alias(cols)
6926                    self.generate_expression(&table.hints[0])?;
6927                    if let Some(ref alias) = table.alias {
6928                        self.write_space();
6929                        if table.alias_explicit_as {
6930                            self.write_keyword("AS");
6931                            self.write_space();
6932                        }
6933                        self.generate_identifier(alias)?;
6934                        if !table.column_aliases.is_empty() {
6935                            self.write("(");
6936                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6937                                if j > 0 {
6938                                    self.write(", ");
6939                                }
6940                                self.generate_identifier(col_alias)?;
6941                            }
6942                            self.write(")");
6943                        }
6944                    }
6945                } else {
6946                    self.generate_table(table)?;
6947                }
6948            }
6949        }
6950
6951        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6952        if delete.tables_from_using {
6953            for join in &delete.joins {
6954                self.generate_join(join)?;
6955            }
6956        }
6957
6958        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6959        let output_already_emitted =
6960            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6961        if !output_already_emitted {
6962            if let Some(ref output) = delete.output {
6963                self.generate_output_clause(output)?;
6964            }
6965        }
6966
6967        if let Some(where_clause) = &delete.where_clause {
6968            self.write_space();
6969            self.write_keyword("WHERE");
6970            self.write_space();
6971            self.generate_expression(&where_clause.this)?;
6972        }
6973
6974        // ORDER BY clause (MySQL)
6975        if let Some(ref order_by) = delete.order_by {
6976            self.write_space();
6977            self.generate_order_by(order_by)?;
6978        }
6979
6980        // LIMIT clause (MySQL)
6981        if let Some(ref limit) = delete.limit {
6982            self.write_space();
6983            self.write_keyword("LIMIT");
6984            self.write_space();
6985            self.generate_expression(limit)?;
6986        }
6987
6988        // RETURNING clause (PostgreSQL)
6989        if !delete.returning.is_empty() {
6990            self.write_space();
6991            self.write_keyword("RETURNING");
6992            self.write_space();
6993            for (i, expr) in delete.returning.iter().enumerate() {
6994                if i > 0 {
6995                    self.write(", ");
6996                }
6997                self.generate_expression(expr)?;
6998            }
6999        }
7000
7001        Ok(())
7002    }
7003
7004    // ==================== DDL Generation ====================
7005
7006    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7007        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7008        // CREATE TABLE AS SELECT uses Trino (double quotes)
7009        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7010        let saved_athena_hive_context = self.athena_hive_context;
7011        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7012        if matches!(
7013            self.config.dialect,
7014            Some(crate::dialects::DialectType::Athena)
7015        ) {
7016            // Use Hive context if:
7017            // 1. It's an EXTERNAL table, OR
7018            // 2. There's no AS SELECT clause
7019            let is_external = ct
7020                .table_modifier
7021                .as_ref()
7022                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7023                .unwrap_or(false);
7024            let has_as_select = ct.as_select.is_some();
7025            self.athena_hive_context = is_external || !has_as_select;
7026        }
7027
7028        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7029        if matches!(
7030            self.config.dialect,
7031            Some(crate::dialects::DialectType::TSQL)
7032        ) {
7033            if let Some(ref query) = ct.as_select {
7034                // Output WITH CTE clause if present
7035                if let Some(with_cte) = &ct.with_cte {
7036                    self.generate_with(with_cte)?;
7037                    self.write_space();
7038                }
7039
7040                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7041                self.write_keyword("SELECT");
7042                self.write(" * ");
7043                self.write_keyword("INTO");
7044                self.write_space();
7045
7046                // If temporary, prefix with # for TSQL temp table
7047                if ct.temporary {
7048                    self.write("#");
7049                }
7050                self.generate_table(&ct.name)?;
7051
7052                self.write_space();
7053                self.write_keyword("FROM");
7054                self.write(" (");
7055                // For TSQL, add aliases to select columns to preserve column names
7056                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7057                self.generate_expression(&aliased_query)?;
7058                self.write(") ");
7059                self.write_keyword("AS");
7060                self.write(" temp");
7061                return Ok(());
7062            }
7063        }
7064
7065        // Output WITH CTE clause if present
7066        if let Some(with_cte) = &ct.with_cte {
7067            self.generate_with(with_cte)?;
7068            self.write_space();
7069        }
7070
7071        // Output leading comments before CREATE
7072        for comment in &ct.leading_comments {
7073            self.write_formatted_comment(comment);
7074            self.write(" ");
7075        }
7076        self.write_keyword("CREATE");
7077
7078        if ct.or_replace {
7079            self.write_space();
7080            self.write_keyword("OR REPLACE");
7081        }
7082
7083        if ct.temporary {
7084            self.write_space();
7085            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7086            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7087                self.write_keyword("GLOBAL TEMPORARY");
7088            } else {
7089                self.write_keyword("TEMPORARY");
7090            }
7091        }
7092
7093        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7094        let is_dictionary = ct
7095            .table_modifier
7096            .as_ref()
7097            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7098            .unwrap_or(false);
7099        if let Some(ref modifier) = ct.table_modifier {
7100            // TRANSIENT is Snowflake-specific - skip for other dialects
7101            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7102                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7103            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7104            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7105                || modifier.eq_ignore_ascii_case("SET")
7106                || modifier.eq_ignore_ascii_case("MULTISET")
7107                || modifier.to_uppercase().contains("VOLATILE")
7108                || modifier.to_uppercase().starts_with("SET ")
7109                || modifier.to_uppercase().starts_with("MULTISET ");
7110            let skip_teradata =
7111                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7112            if !skip_transient && !skip_teradata {
7113                self.write_space();
7114                self.write_keyword(modifier);
7115            }
7116        }
7117
7118        if !is_dictionary {
7119            self.write_space();
7120            self.write_keyword("TABLE");
7121        }
7122
7123        if ct.if_not_exists {
7124            self.write_space();
7125            self.write_keyword("IF NOT EXISTS");
7126        }
7127
7128        self.write_space();
7129        self.generate_table(&ct.name)?;
7130
7131        // ClickHouse: ON CLUSTER clause
7132        if let Some(ref on_cluster) = ct.on_cluster {
7133            self.write_space();
7134            self.generate_on_cluster(on_cluster)?;
7135        }
7136
7137        // Teradata: options after table name before column list (comma-separated)
7138        if matches!(
7139            self.config.dialect,
7140            Some(crate::dialects::DialectType::Teradata)
7141        ) && !ct.teradata_post_name_options.is_empty()
7142        {
7143            for opt in &ct.teradata_post_name_options {
7144                self.write(", ");
7145                self.write(opt);
7146            }
7147        }
7148
7149        // Snowflake: COPY GRANTS clause
7150        if ct.copy_grants {
7151            self.write_space();
7152            self.write_keyword("COPY GRANTS");
7153        }
7154
7155        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7156        if let Some(ref using_template) = ct.using_template {
7157            self.write_space();
7158            self.write_keyword("USING TEMPLATE");
7159            self.write_space();
7160            self.generate_expression(using_template)?;
7161            return Ok(());
7162        }
7163
7164        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7165        if let Some(ref clone_source) = ct.clone_source {
7166            self.write_space();
7167            if ct.is_copy && self.config.supports_table_copy {
7168                // BigQuery uses COPY
7169                self.write_keyword("COPY");
7170            } else if ct.shallow_clone {
7171                self.write_keyword("SHALLOW CLONE");
7172            } else {
7173                self.write_keyword("CLONE");
7174            }
7175            self.write_space();
7176            self.generate_table(clone_source)?;
7177            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7178            if let Some(ref at_clause) = ct.clone_at_clause {
7179                self.write_space();
7180                self.generate_expression(at_clause)?;
7181            }
7182            return Ok(());
7183        }
7184
7185        // Handle PARTITION OF property
7186        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7187        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7188        if let Some(ref partition_of) = ct.partition_of {
7189            self.write_space();
7190
7191            // Extract the PartitionedOfProperty parts to generate them separately
7192            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7193                // Output: PARTITION OF <table>
7194                self.write_keyword("PARTITION OF");
7195                self.write_space();
7196                self.generate_expression(&pop.this)?;
7197
7198                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7199                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7200                    self.write(" (");
7201                    let mut first = true;
7202                    for col in &ct.columns {
7203                        if !first {
7204                            self.write(", ");
7205                        }
7206                        first = false;
7207                        self.generate_column_def(col)?;
7208                    }
7209                    for constraint in &ct.constraints {
7210                        if !first {
7211                            self.write(", ");
7212                        }
7213                        first = false;
7214                        self.generate_table_constraint(constraint)?;
7215                    }
7216                    self.write(")");
7217                }
7218
7219                // Output partition bound spec: FOR VALUES ... or DEFAULT
7220                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7221                    self.write_space();
7222                    self.write_keyword("FOR VALUES");
7223                    self.write_space();
7224                    self.generate_expression(&pop.expression)?;
7225                } else {
7226                    self.write_space();
7227                    self.write_keyword("DEFAULT");
7228                }
7229            } else {
7230                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7231                self.generate_expression(partition_of)?;
7232
7233                // Output columns/constraints if present
7234                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7235                    self.write(" (");
7236                    let mut first = true;
7237                    for col in &ct.columns {
7238                        if !first {
7239                            self.write(", ");
7240                        }
7241                        first = false;
7242                        self.generate_column_def(col)?;
7243                    }
7244                    for constraint in &ct.constraints {
7245                        if !first {
7246                            self.write(", ");
7247                        }
7248                        first = false;
7249                        self.generate_table_constraint(constraint)?;
7250                    }
7251                    self.write(")");
7252                }
7253            }
7254
7255            // Output table properties (e.g., PARTITION BY RANGE(population))
7256            for prop in &ct.properties {
7257                self.write_space();
7258                self.generate_expression(prop)?;
7259            }
7260
7261            return Ok(());
7262        }
7263
7264        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7265        // This matches Python sqlglot's behavior for SQLite dialect
7266        self.sqlite_inline_pk_columns.clear();
7267        if matches!(
7268            self.config.dialect,
7269            Some(crate::dialects::DialectType::SQLite)
7270        ) {
7271            for constraint in &ct.constraints {
7272                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7273                    // Only inline if: single column, no constraint name, and column exists in table
7274                    if columns.len() == 1 && name.is_none() {
7275                        let pk_col_name = columns[0].name.to_lowercase();
7276                        // Check if this column exists in the table
7277                        if ct
7278                            .columns
7279                            .iter()
7280                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7281                        {
7282                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7283                        }
7284                    }
7285                }
7286            }
7287        }
7288
7289        // Output columns if present (even for CTAS with columns)
7290        if !ct.columns.is_empty() {
7291            if self.config.pretty {
7292                // Pretty print: each column on new line
7293                self.write(" (");
7294                self.write_newline();
7295                self.indent_level += 1;
7296                for (i, col) in ct.columns.iter().enumerate() {
7297                    if i > 0 {
7298                        self.write(",");
7299                        self.write_newline();
7300                    }
7301                    self.write_indent();
7302                    self.generate_column_def(col)?;
7303                }
7304                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7305                for constraint in &ct.constraints {
7306                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7307                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7308                        if columns.len() == 1
7309                            && name.is_none()
7310                            && self
7311                                .sqlite_inline_pk_columns
7312                                .contains(&columns[0].name.to_lowercase())
7313                        {
7314                            continue;
7315                        }
7316                    }
7317                    self.write(",");
7318                    self.write_newline();
7319                    self.write_indent();
7320                    self.generate_table_constraint(constraint)?;
7321                }
7322                self.indent_level -= 1;
7323                self.write_newline();
7324                self.write(")");
7325            } else {
7326                self.write(" (");
7327                for (i, col) in ct.columns.iter().enumerate() {
7328                    if i > 0 {
7329                        self.write(", ");
7330                    }
7331                    self.generate_column_def(col)?;
7332                }
7333                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7334                let mut first_constraint = true;
7335                for constraint in &ct.constraints {
7336                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7337                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7338                        if columns.len() == 1
7339                            && name.is_none()
7340                            && self
7341                                .sqlite_inline_pk_columns
7342                                .contains(&columns[0].name.to_lowercase())
7343                        {
7344                            continue;
7345                        }
7346                    }
7347                    if first_constraint {
7348                        self.write(", ");
7349                        first_constraint = false;
7350                    } else {
7351                        self.write(", ");
7352                    }
7353                    self.generate_table_constraint(constraint)?;
7354                }
7355                self.write(")");
7356            }
7357        } else if !ct.constraints.is_empty() {
7358            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7359            let has_like_only = ct
7360                .constraints
7361                .iter()
7362                .all(|c| matches!(c, TableConstraint::Like { .. }));
7363            let has_tags_only = ct
7364                .constraints
7365                .iter()
7366                .all(|c| matches!(c, TableConstraint::Tags(_)));
7367            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7368            // Most dialects: CREATE TABLE A LIKE B (no parens)
7369            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7370            let is_pg_like = matches!(
7371                self.config.dialect,
7372                Some(crate::dialects::DialectType::PostgreSQL)
7373                    | Some(crate::dialects::DialectType::CockroachDB)
7374                    | Some(crate::dialects::DialectType::Materialize)
7375                    | Some(crate::dialects::DialectType::RisingWave)
7376                    | Some(crate::dialects::DialectType::Redshift)
7377                    | Some(crate::dialects::DialectType::Presto)
7378                    | Some(crate::dialects::DialectType::Trino)
7379                    | Some(crate::dialects::DialectType::Athena)
7380            );
7381            let use_parens = if has_like_only {
7382                is_pg_like
7383            } else {
7384                !has_tags_only
7385            };
7386            if self.config.pretty && use_parens {
7387                self.write(" (");
7388                self.write_newline();
7389                self.indent_level += 1;
7390                for (i, constraint) in ct.constraints.iter().enumerate() {
7391                    if i > 0 {
7392                        self.write(",");
7393                        self.write_newline();
7394                    }
7395                    self.write_indent();
7396                    self.generate_table_constraint(constraint)?;
7397                }
7398                self.indent_level -= 1;
7399                self.write_newline();
7400                self.write(")");
7401            } else {
7402                if use_parens {
7403                    self.write(" (");
7404                } else {
7405                    self.write_space();
7406                }
7407                for (i, constraint) in ct.constraints.iter().enumerate() {
7408                    if i > 0 {
7409                        self.write(", ");
7410                    }
7411                    self.generate_table_constraint(constraint)?;
7412                }
7413                if use_parens {
7414                    self.write(")");
7415                }
7416            }
7417        }
7418
7419        // TSQL ON filegroup or ON filegroup (partition_column) clause
7420        if let Some(ref on_prop) = ct.on_property {
7421            self.write(" ");
7422            self.write_keyword("ON");
7423            self.write(" ");
7424            self.generate_expression(&on_prop.this)?;
7425        }
7426
7427        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7428        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7429        if !is_clickhouse {
7430            for prop in &ct.properties {
7431                if let Expression::SchemaCommentProperty(_) = prop {
7432                    if self.config.pretty {
7433                        self.write_newline();
7434                    } else {
7435                        self.write_space();
7436                    }
7437                    self.generate_expression(prop)?;
7438                }
7439            }
7440        }
7441
7442        // WITH properties (output after columns if columns exist, otherwise before AS)
7443        if !ct.with_properties.is_empty() {
7444            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7445            let is_snowflake_special_table = matches!(
7446                self.config.dialect,
7447                Some(crate::dialects::DialectType::Snowflake)
7448            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7449                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7450            if is_snowflake_special_table {
7451                for (key, value) in &ct.with_properties {
7452                    self.write_space();
7453                    self.write(key);
7454                    self.write("=");
7455                    self.write(value);
7456                }
7457            } else if self.config.pretty {
7458                self.write_newline();
7459                self.write_keyword("WITH");
7460                self.write(" (");
7461                self.write_newline();
7462                self.indent_level += 1;
7463                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7464                    if i > 0 {
7465                        self.write(",");
7466                        self.write_newline();
7467                    }
7468                    self.write_indent();
7469                    self.write(key);
7470                    self.write("=");
7471                    self.write(value);
7472                }
7473                self.indent_level -= 1;
7474                self.write_newline();
7475                self.write(")");
7476            } else {
7477                self.write_space();
7478                self.write_keyword("WITH");
7479                self.write(" (");
7480                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7481                    if i > 0 {
7482                        self.write(", ");
7483                    }
7484                    self.write(key);
7485                    self.write("=");
7486                    self.write(value);
7487                }
7488                self.write(")");
7489            }
7490        }
7491
7492        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7493            if is_clickhouse && ct.as_select.is_some() {
7494                let mut pre = Vec::new();
7495                let mut post = Vec::new();
7496                for prop in &ct.properties {
7497                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7498                        post.push(prop);
7499                    } else {
7500                        pre.push(prop);
7501                    }
7502                }
7503                (pre, post)
7504            } else {
7505                (ct.properties.iter().collect(), Vec::new())
7506            };
7507
7508        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7509        for prop in pre_as_properties {
7510            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7511            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7512                continue;
7513            }
7514            if self.config.pretty {
7515                self.write_newline();
7516            } else {
7517                self.write_space();
7518            }
7519            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7520            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7521            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7522            if let Expression::Properties(props) = prop {
7523                let is_hive_dialect = matches!(
7524                    self.config.dialect,
7525                    Some(crate::dialects::DialectType::Hive)
7526                        | Some(crate::dialects::DialectType::Spark)
7527                        | Some(crate::dialects::DialectType::Databricks)
7528                        | Some(crate::dialects::DialectType::Athena)
7529                );
7530                let is_doris_starrocks = matches!(
7531                    self.config.dialect,
7532                    Some(crate::dialects::DialectType::Doris)
7533                        | Some(crate::dialects::DialectType::StarRocks)
7534                );
7535                if is_hive_dialect {
7536                    self.generate_tblproperties_clause(&props.expressions)?;
7537                } else if is_doris_starrocks {
7538                    self.generate_properties_clause(&props.expressions)?;
7539                } else {
7540                    self.generate_options_clause(&props.expressions)?;
7541                }
7542            } else {
7543                self.generate_expression(prop)?;
7544            }
7545        }
7546
7547        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7548        for prop in &ct.post_table_properties {
7549            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7550                self.write(" WITH(");
7551                self.generate_system_versioning_content(svp)?;
7552                self.write(")");
7553            } else if let Expression::Properties(props) = prop {
7554                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7555                let is_doris_starrocks = matches!(
7556                    self.config.dialect,
7557                    Some(crate::dialects::DialectType::Doris)
7558                        | Some(crate::dialects::DialectType::StarRocks)
7559                );
7560                self.write_space();
7561                if is_doris_starrocks {
7562                    self.generate_properties_clause(&props.expressions)?;
7563                } else {
7564                    self.generate_options_clause(&props.expressions)?;
7565                }
7566            } else {
7567                self.write_space();
7568                self.generate_expression(prop)?;
7569            }
7570        }
7571
7572        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7573        // Only output for StarRocks target
7574        if let Some(ref rollup) = ct.rollup {
7575            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7576                self.write_space();
7577                self.generate_rollup_property(rollup)?;
7578            }
7579        }
7580
7581        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7582        // Only output for MySQL-compatible dialects; strip for others during transpilation
7583        // COMMENT is also used by Hive/Spark so we selectively preserve it
7584        let is_mysql_compatible = matches!(
7585            self.config.dialect,
7586            Some(DialectType::MySQL)
7587                | Some(DialectType::SingleStore)
7588                | Some(DialectType::Doris)
7589                | Some(DialectType::StarRocks)
7590                | None
7591        );
7592        let is_hive_compatible = matches!(
7593            self.config.dialect,
7594            Some(DialectType::Hive)
7595                | Some(DialectType::Spark)
7596                | Some(DialectType::Databricks)
7597                | Some(DialectType::Athena)
7598        );
7599        let mysql_pretty_options =
7600            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7601        for (key, value) in &ct.mysql_table_options {
7602            // Skip non-MySQL-specific options for non-MySQL targets
7603            let should_output = if is_mysql_compatible {
7604                true
7605            } else if is_hive_compatible && key == "COMMENT" {
7606                true // COMMENT is valid in Hive/Spark table definitions
7607            } else {
7608                false
7609            };
7610            if should_output {
7611                if mysql_pretty_options {
7612                    self.write_newline();
7613                    self.write_indent();
7614                } else {
7615                    self.write_space();
7616                }
7617                self.write_keyword(key);
7618                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7619                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7620                    self.write_space();
7621                } else {
7622                    self.write("=");
7623                }
7624                self.write(value);
7625            }
7626        }
7627
7628        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7629        if ct.temporary
7630            && matches!(
7631                self.config.dialect,
7632                Some(DialectType::Spark) | Some(DialectType::Databricks)
7633            )
7634            && ct.as_select.is_none()
7635        {
7636            self.write_space();
7637            self.write_keyword("USING PARQUET");
7638        }
7639
7640        // PostgreSQL INHERITS clause
7641        if !ct.inherits.is_empty() {
7642            self.write_space();
7643            self.write_keyword("INHERITS");
7644            self.write(" (");
7645            for (i, parent) in ct.inherits.iter().enumerate() {
7646                if i > 0 {
7647                    self.write(", ");
7648                }
7649                self.generate_table(parent)?;
7650            }
7651            self.write(")");
7652        }
7653
7654        // CREATE TABLE AS SELECT
7655        if let Some(ref query) = ct.as_select {
7656            self.write_space();
7657            self.write_keyword("AS");
7658            self.write_space();
7659            if ct.as_select_parenthesized {
7660                self.write("(");
7661            }
7662            self.generate_expression(query)?;
7663            if ct.as_select_parenthesized {
7664                self.write(")");
7665            }
7666
7667            // Teradata: WITH DATA / WITH NO DATA
7668            if let Some(with_data) = ct.with_data {
7669                self.write_space();
7670                self.write_keyword("WITH");
7671                if !with_data {
7672                    self.write_space();
7673                    self.write_keyword("NO");
7674                }
7675                self.write_space();
7676                self.write_keyword("DATA");
7677            }
7678
7679            // Teradata: AND STATISTICS / AND NO STATISTICS
7680            if let Some(with_statistics) = ct.with_statistics {
7681                self.write_space();
7682                self.write_keyword("AND");
7683                if !with_statistics {
7684                    self.write_space();
7685                    self.write_keyword("NO");
7686                }
7687                self.write_space();
7688                self.write_keyword("STATISTICS");
7689            }
7690
7691            // Teradata: Index specifications
7692            for index in &ct.teradata_indexes {
7693                self.write_space();
7694                match index.kind {
7695                    TeradataIndexKind::NoPrimary => {
7696                        self.write_keyword("NO PRIMARY INDEX");
7697                    }
7698                    TeradataIndexKind::Primary => {
7699                        self.write_keyword("PRIMARY INDEX");
7700                    }
7701                    TeradataIndexKind::PrimaryAmp => {
7702                        self.write_keyword("PRIMARY AMP INDEX");
7703                    }
7704                    TeradataIndexKind::Unique => {
7705                        self.write_keyword("UNIQUE INDEX");
7706                    }
7707                    TeradataIndexKind::UniquePrimary => {
7708                        self.write_keyword("UNIQUE PRIMARY INDEX");
7709                    }
7710                    TeradataIndexKind::Secondary => {
7711                        self.write_keyword("INDEX");
7712                    }
7713                }
7714                // Output index name if present
7715                if let Some(ref name) = index.name {
7716                    self.write_space();
7717                    self.write(name);
7718                }
7719                // Output columns if present
7720                if !index.columns.is_empty() {
7721                    self.write(" (");
7722                    for (i, col) in index.columns.iter().enumerate() {
7723                        if i > 0 {
7724                            self.write(", ");
7725                        }
7726                        self.write(col);
7727                    }
7728                    self.write(")");
7729                }
7730            }
7731
7732            // Teradata: ON COMMIT behavior for volatile tables
7733            if let Some(ref on_commit) = ct.on_commit {
7734                self.write_space();
7735                self.write_keyword("ON COMMIT");
7736                self.write_space();
7737                match on_commit {
7738                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7739                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7740                }
7741            }
7742
7743            if !post_as_properties.is_empty() {
7744                for prop in post_as_properties {
7745                    self.write_space();
7746                    self.generate_expression(prop)?;
7747                }
7748            }
7749
7750            // Restore Athena Hive context before early return
7751            self.athena_hive_context = saved_athena_hive_context;
7752            return Ok(());
7753        }
7754
7755        // ON COMMIT behavior (for non-CTAS tables)
7756        if let Some(ref on_commit) = ct.on_commit {
7757            self.write_space();
7758            self.write_keyword("ON COMMIT");
7759            self.write_space();
7760            match on_commit {
7761                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7762                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7763            }
7764        }
7765
7766        // Restore Athena Hive context
7767        self.athena_hive_context = saved_athena_hive_context;
7768
7769        Ok(())
7770    }
7771
7772    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7773    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7774    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7775        // Output column name
7776        self.generate_identifier(&col.name)?;
7777        // Output data type if known
7778        if !matches!(col.data_type, DataType::Unknown) {
7779            self.write_space();
7780            self.generate_data_type(&col.data_type)?;
7781        }
7782        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7783        for constraint in &col.constraints {
7784            if let ColumnConstraint::Path(path_expr) = constraint {
7785                self.write_space();
7786                self.write_keyword("PATH");
7787                self.write_space();
7788                self.generate_expression(path_expr)?;
7789            }
7790        }
7791        Ok(())
7792    }
7793
7794    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7795        // Check if this is a TSQL computed column (no data type)
7796        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7797            && col
7798                .constraints
7799                .iter()
7800                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7801        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7802        let omit_computed_type = !self.config.computed_column_with_type
7803            && col
7804                .constraints
7805                .iter()
7806                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7807
7808        // Check if this is a partition column spec (no data type, type is Unknown)
7809        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7810        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7811
7812        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7813        // Also check the no_type flag for SQLite columns without types
7814        let has_no_type = col.no_type
7815            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7816                && col.constraints.is_empty());
7817
7818        self.generate_identifier(&col.name)?;
7819
7820        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7821        let serial_expansion = if matches!(
7822            self.config.dialect,
7823            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7824        ) {
7825            if let DataType::Custom { ref name } = col.data_type {
7826                match name.to_uppercase().as_str() {
7827                    "SERIAL" => Some("INT"),
7828                    "BIGSERIAL" => Some("BIGINT"),
7829                    "SMALLSERIAL" => Some("SMALLINT"),
7830                    _ => None,
7831                }
7832            } else {
7833                None
7834            }
7835        } else {
7836            None
7837        };
7838
7839        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7840        {
7841            self.write_space();
7842            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7843            // since ClickHouse uses explicit Nullable() in its type system.
7844            let saved_nullable_depth = self.clickhouse_nullable_depth;
7845            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7846                self.clickhouse_nullable_depth = -1;
7847            }
7848            if let Some(int_type) = serial_expansion {
7849                // SERIAL -> INT (+ constraints added below)
7850                self.write_keyword(int_type);
7851            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7852                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7853                let unsigned_type = match &col.data_type {
7854                    DataType::Int { .. } => Some("UINTEGER"),
7855                    DataType::BigInt { .. } => Some("UBIGINT"),
7856                    DataType::SmallInt { .. } => Some("USMALLINT"),
7857                    DataType::TinyInt { .. } => Some("UTINYINT"),
7858                    _ => None,
7859                };
7860                if let Some(utype) = unsigned_type {
7861                    self.write_keyword(utype);
7862                } else {
7863                    self.generate_data_type(&col.data_type)?;
7864                }
7865            } else {
7866                self.generate_data_type(&col.data_type)?;
7867            }
7868            self.clickhouse_nullable_depth = saved_nullable_depth;
7869        }
7870
7871        // MySQL type modifiers (must come right after data type)
7872        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7873        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7874            self.write_space();
7875            self.write_keyword("UNSIGNED");
7876        }
7877        if col.zerofill {
7878            self.write_space();
7879            self.write_keyword("ZEROFILL");
7880        }
7881
7882        // Teradata column attributes (must come right after data type, in specific order)
7883        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7884
7885        if let Some(ref charset) = col.character_set {
7886            self.write_space();
7887            self.write_keyword("CHARACTER SET");
7888            self.write_space();
7889            self.write(charset);
7890        }
7891
7892        if col.uppercase {
7893            self.write_space();
7894            self.write_keyword("UPPERCASE");
7895        }
7896
7897        if let Some(casespecific) = col.casespecific {
7898            self.write_space();
7899            if casespecific {
7900                self.write_keyword("CASESPECIFIC");
7901            } else {
7902                self.write_keyword("NOT CASESPECIFIC");
7903            }
7904        }
7905
7906        if let Some(ref format) = col.format {
7907            self.write_space();
7908            self.write_keyword("FORMAT");
7909            self.write(" '");
7910            self.write(format);
7911            self.write("'");
7912        }
7913
7914        if let Some(ref title) = col.title {
7915            self.write_space();
7916            self.write_keyword("TITLE");
7917            self.write(" '");
7918            self.write(title);
7919            self.write("'");
7920        }
7921
7922        if let Some(length) = col.inline_length {
7923            self.write_space();
7924            self.write_keyword("INLINE LENGTH");
7925            self.write(" ");
7926            self.write(&length.to_string());
7927        }
7928
7929        if let Some(ref compress) = col.compress {
7930            self.write_space();
7931            self.write_keyword("COMPRESS");
7932            if !compress.is_empty() {
7933                // Single string literal: output without parentheses (Teradata syntax)
7934                if compress.len() == 1 {
7935                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7936                        self.write_space();
7937                        self.generate_expression(&compress[0])?;
7938                    } else {
7939                        self.write(" (");
7940                        self.generate_expression(&compress[0])?;
7941                        self.write(")");
7942                    }
7943                } else {
7944                    self.write(" (");
7945                    for (i, val) in compress.iter().enumerate() {
7946                        if i > 0 {
7947                            self.write(", ");
7948                        }
7949                        self.generate_expression(val)?;
7950                    }
7951                    self.write(")");
7952                }
7953            }
7954        }
7955
7956        // Column constraints - output in original order if constraint_order is populated
7957        // Otherwise fall back to legacy fixed order for backward compatibility
7958        if !col.constraint_order.is_empty() {
7959            // Use constraint_order for original ordering
7960            // Track indices for constraints stored in the constraints Vec
7961            let mut references_idx = 0;
7962            let mut check_idx = 0;
7963            let mut generated_idx = 0;
7964            let mut collate_idx = 0;
7965            let mut comment_idx = 0;
7966            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7967            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7968            let defer_not_null_after_identity = false;
7969            let mut pending_not_null_after_identity = false;
7970
7971            for constraint_type in &col.constraint_order {
7972                match constraint_type {
7973                    ConstraintType::PrimaryKey => {
7974                        // Materialize doesn't support PRIMARY KEY column constraints
7975                        if col.primary_key
7976                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7977                        {
7978                            if let Some(ref cname) = col.primary_key_constraint_name {
7979                                self.write_space();
7980                                self.write_keyword("CONSTRAINT");
7981                                self.write_space();
7982                                self.write(cname);
7983                            }
7984                            self.write_space();
7985                            self.write_keyword("PRIMARY KEY");
7986                            if let Some(ref order) = col.primary_key_order {
7987                                self.write_space();
7988                                match order {
7989                                    SortOrder::Asc => self.write_keyword("ASC"),
7990                                    SortOrder::Desc => self.write_keyword("DESC"),
7991                                }
7992                            }
7993                        }
7994                    }
7995                    ConstraintType::Unique => {
7996                        if col.unique {
7997                            if let Some(ref cname) = col.unique_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("UNIQUE");
8005                            // PostgreSQL 15+: NULLS NOT DISTINCT
8006                            if col.unique_nulls_not_distinct {
8007                                self.write(" NULLS NOT DISTINCT");
8008                            }
8009                        }
8010                    }
8011                    ConstraintType::NotNull => {
8012                        if col.nullable == Some(false) {
8013                            if defer_not_null_after_identity {
8014                                pending_not_null_after_identity = true;
8015                                continue;
8016                            }
8017                            if let Some(ref cname) = col.not_null_constraint_name {
8018                                self.write_space();
8019                                self.write_keyword("CONSTRAINT");
8020                                self.write_space();
8021                                self.write(cname);
8022                            }
8023                            self.write_space();
8024                            self.write_keyword("NOT NULL");
8025                        }
8026                    }
8027                    ConstraintType::Null => {
8028                        if col.nullable == Some(true) {
8029                            self.write_space();
8030                            self.write_keyword("NULL");
8031                        }
8032                    }
8033                    ConstraintType::Default => {
8034                        if let Some(ref default) = col.default {
8035                            self.write_space();
8036                            self.write_keyword("DEFAULT");
8037                            self.write_space();
8038                            self.generate_expression(default)?;
8039                        }
8040                    }
8041                    ConstraintType::AutoIncrement => {
8042                        if col.auto_increment {
8043                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8044                            if matches!(
8045                                self.config.dialect,
8046                                Some(crate::dialects::DialectType::DuckDB)
8047                            ) {
8048                                // Skip - DuckDB uses sequences or rowid instead
8049                            } else if matches!(
8050                                self.config.dialect,
8051                                Some(crate::dialects::DialectType::Materialize)
8052                            ) {
8053                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8054                                if !matches!(col.nullable, Some(false)) {
8055                                    self.write_space();
8056                                    self.write_keyword("NOT NULL");
8057                                }
8058                            } else if matches!(
8059                                self.config.dialect,
8060                                Some(crate::dialects::DialectType::PostgreSQL)
8061                            ) {
8062                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8063                                self.write_space();
8064                                self.generate_auto_increment_keyword(col)?;
8065                            } else {
8066                                self.write_space();
8067                                self.generate_auto_increment_keyword(col)?;
8068                                if pending_not_null_after_identity {
8069                                    self.write_space();
8070                                    self.write_keyword("NOT NULL");
8071                                    pending_not_null_after_identity = false;
8072                                }
8073                            }
8074                        } // close else for DuckDB skip
8075                    }
8076                    ConstraintType::References => {
8077                        // Find next References constraint
8078                        while references_idx < col.constraints.len() {
8079                            if let ColumnConstraint::References(fk_ref) =
8080                                &col.constraints[references_idx]
8081                            {
8082                                // CONSTRAINT name if present
8083                                if let Some(ref name) = fk_ref.constraint_name {
8084                                    self.write_space();
8085                                    self.write_keyword("CONSTRAINT");
8086                                    self.write_space();
8087                                    self.write(name);
8088                                }
8089                                self.write_space();
8090                                if fk_ref.has_foreign_key_keywords {
8091                                    self.write_keyword("FOREIGN KEY");
8092                                    self.write_space();
8093                                }
8094                                self.write_keyword("REFERENCES");
8095                                self.write_space();
8096                                self.generate_table(&fk_ref.table)?;
8097                                if !fk_ref.columns.is_empty() {
8098                                    self.write(" (");
8099                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8100                                        if i > 0 {
8101                                            self.write(", ");
8102                                        }
8103                                        self.generate_identifier(c)?;
8104                                    }
8105                                    self.write(")");
8106                                }
8107                                self.generate_referential_actions(fk_ref)?;
8108                                references_idx += 1;
8109                                break;
8110                            }
8111                            references_idx += 1;
8112                        }
8113                    }
8114                    ConstraintType::Check => {
8115                        // Find next Check constraint
8116                        while check_idx < col.constraints.len() {
8117                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8118                                // Output CONSTRAINT name if present (only for first CHECK)
8119                                if check_idx == 0 {
8120                                    if let Some(ref cname) = col.check_constraint_name {
8121                                        self.write_space();
8122                                        self.write_keyword("CONSTRAINT");
8123                                        self.write_space();
8124                                        self.write(cname);
8125                                    }
8126                                }
8127                                self.write_space();
8128                                self.write_keyword("CHECK");
8129                                self.write(" (");
8130                                self.generate_expression(expr)?;
8131                                self.write(")");
8132                                check_idx += 1;
8133                                break;
8134                            }
8135                            check_idx += 1;
8136                        }
8137                    }
8138                    ConstraintType::GeneratedAsIdentity => {
8139                        // Find next GeneratedAsIdentity constraint
8140                        while generated_idx < col.constraints.len() {
8141                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8142                                &col.constraints[generated_idx]
8143                            {
8144                                self.write_space();
8145                                // Redshift uses IDENTITY(start, increment) syntax
8146                                if matches!(
8147                                    self.config.dialect,
8148                                    Some(crate::dialects::DialectType::Redshift)
8149                                ) {
8150                                    self.write_keyword("IDENTITY");
8151                                    self.write("(");
8152                                    if let Some(ref start) = gen.start {
8153                                        self.generate_expression(start)?;
8154                                    } else {
8155                                        self.write("0");
8156                                    }
8157                                    self.write(", ");
8158                                    if let Some(ref incr) = gen.increment {
8159                                        self.generate_expression(incr)?;
8160                                    } else {
8161                                        self.write("1");
8162                                    }
8163                                    self.write(")");
8164                                } else {
8165                                    self.write_keyword("GENERATED");
8166                                    if gen.always {
8167                                        self.write_space();
8168                                        self.write_keyword("ALWAYS");
8169                                    } else {
8170                                        self.write_space();
8171                                        self.write_keyword("BY DEFAULT");
8172                                        if gen.on_null {
8173                                            self.write_space();
8174                                            self.write_keyword("ON NULL");
8175                                        }
8176                                    }
8177                                    self.write_space();
8178                                    self.write_keyword("AS IDENTITY");
8179
8180                                    let has_options = gen.start.is_some()
8181                                        || gen.increment.is_some()
8182                                        || gen.minvalue.is_some()
8183                                        || gen.maxvalue.is_some()
8184                                        || gen.cycle.is_some();
8185                                    if has_options {
8186                                        self.write(" (");
8187                                        let mut first = true;
8188                                        if let Some(ref start) = gen.start {
8189                                            if !first {
8190                                                self.write(" ");
8191                                            }
8192                                            first = false;
8193                                            self.write_keyword("START WITH");
8194                                            self.write_space();
8195                                            self.generate_expression(start)?;
8196                                        }
8197                                        if let Some(ref incr) = gen.increment {
8198                                            if !first {
8199                                                self.write(" ");
8200                                            }
8201                                            first = false;
8202                                            self.write_keyword("INCREMENT BY");
8203                                            self.write_space();
8204                                            self.generate_expression(incr)?;
8205                                        }
8206                                        if let Some(ref minv) = gen.minvalue {
8207                                            if !first {
8208                                                self.write(" ");
8209                                            }
8210                                            first = false;
8211                                            self.write_keyword("MINVALUE");
8212                                            self.write_space();
8213                                            self.generate_expression(minv)?;
8214                                        }
8215                                        if let Some(ref maxv) = gen.maxvalue {
8216                                            if !first {
8217                                                self.write(" ");
8218                                            }
8219                                            first = false;
8220                                            self.write_keyword("MAXVALUE");
8221                                            self.write_space();
8222                                            self.generate_expression(maxv)?;
8223                                        }
8224                                        if let Some(cycle) = gen.cycle {
8225                                            if !first {
8226                                                self.write(" ");
8227                                            }
8228                                            if cycle {
8229                                                self.write_keyword("CYCLE");
8230                                            } else {
8231                                                self.write_keyword("NO CYCLE");
8232                                            }
8233                                        }
8234                                        self.write(")");
8235                                    }
8236                                }
8237                                generated_idx += 1;
8238                                break;
8239                            }
8240                            generated_idx += 1;
8241                        }
8242                    }
8243                    ConstraintType::Collate => {
8244                        // Find next Collate constraint
8245                        while collate_idx < col.constraints.len() {
8246                            if let ColumnConstraint::Collate(collation) =
8247                                &col.constraints[collate_idx]
8248                            {
8249                                self.write_space();
8250                                self.write_keyword("COLLATE");
8251                                self.write_space();
8252                                self.generate_identifier(collation)?;
8253                                collate_idx += 1;
8254                                break;
8255                            }
8256                            collate_idx += 1;
8257                        }
8258                    }
8259                    ConstraintType::Comment => {
8260                        // Find next Comment constraint
8261                        while comment_idx < col.constraints.len() {
8262                            if let ColumnConstraint::Comment(comment) =
8263                                &col.constraints[comment_idx]
8264                            {
8265                                self.write_space();
8266                                self.write_keyword("COMMENT");
8267                                self.write_space();
8268                                self.generate_string_literal(comment)?;
8269                                comment_idx += 1;
8270                                break;
8271                            }
8272                            comment_idx += 1;
8273                        }
8274                    }
8275                    ConstraintType::Tags => {
8276                        // Find next Tags constraint (Snowflake)
8277                        for constraint in &col.constraints {
8278                            if let ColumnConstraint::Tags(tags) = constraint {
8279                                self.write_space();
8280                                self.write_keyword("TAG");
8281                                self.write(" (");
8282                                for (i, expr) in tags.expressions.iter().enumerate() {
8283                                    if i > 0 {
8284                                        self.write(", ");
8285                                    }
8286                                    self.generate_expression(expr)?;
8287                                }
8288                                self.write(")");
8289                                break;
8290                            }
8291                        }
8292                    }
8293                    ConstraintType::ComputedColumn => {
8294                        // Find next ComputedColumn constraint
8295                        for constraint in &col.constraints {
8296                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8297                                self.write_space();
8298                                self.generate_computed_column_inline(cc)?;
8299                                break;
8300                            }
8301                        }
8302                    }
8303                    ConstraintType::GeneratedAsRow => {
8304                        // Find next GeneratedAsRow constraint
8305                        for constraint in &col.constraints {
8306                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8307                                self.write_space();
8308                                self.generate_generated_as_row_inline(gar)?;
8309                                break;
8310                            }
8311                        }
8312                    }
8313                    ConstraintType::OnUpdate => {
8314                        if let Some(ref expr) = col.on_update {
8315                            self.write_space();
8316                            self.write_keyword("ON UPDATE");
8317                            self.write_space();
8318                            self.generate_expression(expr)?;
8319                        }
8320                    }
8321                    ConstraintType::Encode => {
8322                        if let Some(ref encoding) = col.encoding {
8323                            self.write_space();
8324                            self.write_keyword("ENCODE");
8325                            self.write_space();
8326                            self.write(encoding);
8327                        }
8328                    }
8329                    ConstraintType::Path => {
8330                        // Find next Path constraint
8331                        for constraint in &col.constraints {
8332                            if let ColumnConstraint::Path(path_expr) = constraint {
8333                                self.write_space();
8334                                self.write_keyword("PATH");
8335                                self.write_space();
8336                                self.generate_expression(path_expr)?;
8337                                break;
8338                            }
8339                        }
8340                    }
8341                }
8342            }
8343            if pending_not_null_after_identity {
8344                self.write_space();
8345                self.write_keyword("NOT NULL");
8346            }
8347        } else {
8348            // Legacy fixed order for backward compatibility
8349            if col.primary_key {
8350                self.write_space();
8351                self.write_keyword("PRIMARY KEY");
8352                if let Some(ref order) = col.primary_key_order {
8353                    self.write_space();
8354                    match order {
8355                        SortOrder::Asc => self.write_keyword("ASC"),
8356                        SortOrder::Desc => self.write_keyword("DESC"),
8357                    }
8358                }
8359            }
8360
8361            if col.unique {
8362                self.write_space();
8363                self.write_keyword("UNIQUE");
8364                // PostgreSQL 15+: NULLS NOT DISTINCT
8365                if col.unique_nulls_not_distinct {
8366                    self.write(" NULLS NOT DISTINCT");
8367                }
8368            }
8369
8370            match col.nullable {
8371                Some(false) => {
8372                    self.write_space();
8373                    self.write_keyword("NOT NULL");
8374                }
8375                Some(true) => {
8376                    self.write_space();
8377                    self.write_keyword("NULL");
8378                }
8379                None => {}
8380            }
8381
8382            if let Some(ref default) = col.default {
8383                self.write_space();
8384                self.write_keyword("DEFAULT");
8385                self.write_space();
8386                self.generate_expression(default)?;
8387            }
8388
8389            if col.auto_increment {
8390                self.write_space();
8391                self.generate_auto_increment_keyword(col)?;
8392            }
8393
8394            // Column-level constraints from Vec
8395            for constraint in &col.constraints {
8396                match constraint {
8397                    ColumnConstraint::References(fk_ref) => {
8398                        self.write_space();
8399                        if fk_ref.has_foreign_key_keywords {
8400                            self.write_keyword("FOREIGN KEY");
8401                            self.write_space();
8402                        }
8403                        self.write_keyword("REFERENCES");
8404                        self.write_space();
8405                        self.generate_table(&fk_ref.table)?;
8406                        if !fk_ref.columns.is_empty() {
8407                            self.write(" (");
8408                            for (i, c) in fk_ref.columns.iter().enumerate() {
8409                                if i > 0 {
8410                                    self.write(", ");
8411                                }
8412                                self.generate_identifier(c)?;
8413                            }
8414                            self.write(")");
8415                        }
8416                        self.generate_referential_actions(fk_ref)?;
8417                    }
8418                    ColumnConstraint::Check(expr) => {
8419                        self.write_space();
8420                        self.write_keyword("CHECK");
8421                        self.write(" (");
8422                        self.generate_expression(expr)?;
8423                        self.write(")");
8424                    }
8425                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8426                        self.write_space();
8427                        // Redshift uses IDENTITY(start, increment) syntax
8428                        if matches!(
8429                            self.config.dialect,
8430                            Some(crate::dialects::DialectType::Redshift)
8431                        ) {
8432                            self.write_keyword("IDENTITY");
8433                            self.write("(");
8434                            if let Some(ref start) = gen.start {
8435                                self.generate_expression(start)?;
8436                            } else {
8437                                self.write("0");
8438                            }
8439                            self.write(", ");
8440                            if let Some(ref incr) = gen.increment {
8441                                self.generate_expression(incr)?;
8442                            } else {
8443                                self.write("1");
8444                            }
8445                            self.write(")");
8446                        } else {
8447                            self.write_keyword("GENERATED");
8448                            if gen.always {
8449                                self.write_space();
8450                                self.write_keyword("ALWAYS");
8451                            } else {
8452                                self.write_space();
8453                                self.write_keyword("BY DEFAULT");
8454                                if gen.on_null {
8455                                    self.write_space();
8456                                    self.write_keyword("ON NULL");
8457                                }
8458                            }
8459                            self.write_space();
8460                            self.write_keyword("AS IDENTITY");
8461
8462                            let has_options = gen.start.is_some()
8463                                || gen.increment.is_some()
8464                                || gen.minvalue.is_some()
8465                                || gen.maxvalue.is_some()
8466                                || gen.cycle.is_some();
8467                            if has_options {
8468                                self.write(" (");
8469                                let mut first = true;
8470                                if let Some(ref start) = gen.start {
8471                                    if !first {
8472                                        self.write(" ");
8473                                    }
8474                                    first = false;
8475                                    self.write_keyword("START WITH");
8476                                    self.write_space();
8477                                    self.generate_expression(start)?;
8478                                }
8479                                if let Some(ref incr) = gen.increment {
8480                                    if !first {
8481                                        self.write(" ");
8482                                    }
8483                                    first = false;
8484                                    self.write_keyword("INCREMENT BY");
8485                                    self.write_space();
8486                                    self.generate_expression(incr)?;
8487                                }
8488                                if let Some(ref minv) = gen.minvalue {
8489                                    if !first {
8490                                        self.write(" ");
8491                                    }
8492                                    first = false;
8493                                    self.write_keyword("MINVALUE");
8494                                    self.write_space();
8495                                    self.generate_expression(minv)?;
8496                                }
8497                                if let Some(ref maxv) = gen.maxvalue {
8498                                    if !first {
8499                                        self.write(" ");
8500                                    }
8501                                    first = false;
8502                                    self.write_keyword("MAXVALUE");
8503                                    self.write_space();
8504                                    self.generate_expression(maxv)?;
8505                                }
8506                                if let Some(cycle) = gen.cycle {
8507                                    if !first {
8508                                        self.write(" ");
8509                                    }
8510                                    if cycle {
8511                                        self.write_keyword("CYCLE");
8512                                    } else {
8513                                        self.write_keyword("NO CYCLE");
8514                                    }
8515                                }
8516                                self.write(")");
8517                            }
8518                        }
8519                    }
8520                    ColumnConstraint::Collate(collation) => {
8521                        self.write_space();
8522                        self.write_keyword("COLLATE");
8523                        self.write_space();
8524                        self.generate_identifier(collation)?;
8525                    }
8526                    ColumnConstraint::Comment(comment) => {
8527                        self.write_space();
8528                        self.write_keyword("COMMENT");
8529                        self.write_space();
8530                        self.generate_string_literal(comment)?;
8531                    }
8532                    ColumnConstraint::Path(path_expr) => {
8533                        self.write_space();
8534                        self.write_keyword("PATH");
8535                        self.write_space();
8536                        self.generate_expression(path_expr)?;
8537                    }
8538                    _ => {} // Other constraints handled above
8539                }
8540            }
8541
8542            // Redshift: ENCODE encoding_type (legacy path)
8543            if let Some(ref encoding) = col.encoding {
8544                self.write_space();
8545                self.write_keyword("ENCODE");
8546                self.write_space();
8547                self.write(encoding);
8548            }
8549        }
8550
8551        // ClickHouse: CODEC(...)
8552        if let Some(ref codec) = col.codec {
8553            self.write_space();
8554            self.write_keyword("CODEC");
8555            self.write("(");
8556            self.write(codec);
8557            self.write(")");
8558        }
8559
8560        // ClickHouse: EPHEMERAL [expr]
8561        if let Some(ref ephemeral) = col.ephemeral {
8562            self.write_space();
8563            self.write_keyword("EPHEMERAL");
8564            if let Some(ref expr) = ephemeral {
8565                self.write_space();
8566                self.generate_expression(expr)?;
8567            }
8568        }
8569
8570        // ClickHouse: MATERIALIZED expr
8571        if let Some(ref mat_expr) = col.materialized_expr {
8572            self.write_space();
8573            self.write_keyword("MATERIALIZED");
8574            self.write_space();
8575            self.generate_expression(mat_expr)?;
8576        }
8577
8578        // ClickHouse: ALIAS expr
8579        if let Some(ref alias_expr) = col.alias_expr {
8580            self.write_space();
8581            self.write_keyword("ALIAS");
8582            self.write_space();
8583            self.generate_expression(alias_expr)?;
8584        }
8585
8586        // ClickHouse: TTL expr
8587        if let Some(ref ttl_expr) = col.ttl_expr {
8588            self.write_space();
8589            self.write_keyword("TTL");
8590            self.write_space();
8591            self.generate_expression(ttl_expr)?;
8592        }
8593
8594        // TSQL: NOT FOR REPLICATION
8595        if col.not_for_replication
8596            && matches!(
8597                self.config.dialect,
8598                Some(crate::dialects::DialectType::TSQL)
8599                    | Some(crate::dialects::DialectType::Fabric)
8600            )
8601        {
8602            self.write_space();
8603            self.write_keyword("NOT FOR REPLICATION");
8604        }
8605
8606        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8607        if !col.options.is_empty() {
8608            self.write_space();
8609            self.generate_options_clause(&col.options)?;
8610        }
8611
8612        // SQLite: Inline PRIMARY KEY from table constraint
8613        // This comes at the end, after all existing column constraints
8614        if !col.primary_key
8615            && self
8616                .sqlite_inline_pk_columns
8617                .contains(&col.name.name.to_lowercase())
8618        {
8619            self.write_space();
8620            self.write_keyword("PRIMARY KEY");
8621        }
8622
8623        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8624        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8625        if serial_expansion.is_some() {
8626            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8627                self.write_space();
8628                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8629            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8630                self.write_space();
8631                self.write_keyword("NOT NULL");
8632            }
8633        }
8634
8635        Ok(())
8636    }
8637
8638    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8639        match constraint {
8640            TableConstraint::PrimaryKey {
8641                name,
8642                columns,
8643                include_columns,
8644                modifiers,
8645                has_constraint_keyword,
8646            } => {
8647                if let Some(ref n) = name {
8648                    if *has_constraint_keyword {
8649                        self.write_keyword("CONSTRAINT");
8650                        self.write_space();
8651                        self.generate_identifier(n)?;
8652                        self.write_space();
8653                    }
8654                }
8655                self.write_keyword("PRIMARY KEY");
8656                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8657                if let Some(ref clustered) = modifiers.clustered {
8658                    self.write_space();
8659                    self.write_keyword(clustered);
8660                }
8661                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8662                if let Some(ref n) = name {
8663                    if !*has_constraint_keyword {
8664                        self.write_space();
8665                        self.generate_identifier(n)?;
8666                    }
8667                }
8668                self.write(" (");
8669                for (i, col) in columns.iter().enumerate() {
8670                    if i > 0 {
8671                        self.write(", ");
8672                    }
8673                    self.generate_identifier(col)?;
8674                }
8675                self.write(")");
8676                if !include_columns.is_empty() {
8677                    self.write_space();
8678                    self.write_keyword("INCLUDE");
8679                    self.write(" (");
8680                    for (i, col) in include_columns.iter().enumerate() {
8681                        if i > 0 {
8682                            self.write(", ");
8683                        }
8684                        self.generate_identifier(col)?;
8685                    }
8686                    self.write(")");
8687                }
8688                self.generate_constraint_modifiers(modifiers);
8689            }
8690            TableConstraint::Unique {
8691                name,
8692                columns,
8693                columns_parenthesized,
8694                modifiers,
8695                has_constraint_keyword,
8696                nulls_not_distinct,
8697            } => {
8698                if let Some(ref n) = name {
8699                    if *has_constraint_keyword {
8700                        self.write_keyword("CONSTRAINT");
8701                        self.write_space();
8702                        self.generate_identifier(n)?;
8703                        self.write_space();
8704                    }
8705                }
8706                self.write_keyword("UNIQUE");
8707                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8708                if let Some(ref clustered) = modifiers.clustered {
8709                    self.write_space();
8710                    self.write_keyword(clustered);
8711                }
8712                // PostgreSQL 15+: NULLS NOT DISTINCT
8713                if *nulls_not_distinct {
8714                    self.write(" NULLS NOT DISTINCT");
8715                }
8716                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8717                if let Some(ref n) = name {
8718                    if !*has_constraint_keyword {
8719                        self.write_space();
8720                        self.generate_identifier(n)?;
8721                    }
8722                }
8723                if *columns_parenthesized {
8724                    self.write(" (");
8725                    for (i, col) in columns.iter().enumerate() {
8726                        if i > 0 {
8727                            self.write(", ");
8728                        }
8729                        self.generate_identifier(col)?;
8730                    }
8731                    self.write(")");
8732                } else {
8733                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8734                    for col in columns.iter() {
8735                        self.write_space();
8736                        self.generate_identifier(col)?;
8737                    }
8738                }
8739                self.generate_constraint_modifiers(modifiers);
8740            }
8741            TableConstraint::ForeignKey {
8742                name,
8743                columns,
8744                references,
8745                on_delete,
8746                on_update,
8747                modifiers,
8748            } => {
8749                if let Some(ref n) = name {
8750                    self.write_keyword("CONSTRAINT");
8751                    self.write_space();
8752                    self.generate_identifier(n)?;
8753                    self.write_space();
8754                }
8755                self.write_keyword("FOREIGN KEY");
8756                self.write(" (");
8757                for (i, col) in columns.iter().enumerate() {
8758                    if i > 0 {
8759                        self.write(", ");
8760                    }
8761                    self.generate_identifier(col)?;
8762                }
8763                self.write(")");
8764                if let Some(ref refs) = references {
8765                    self.write(" ");
8766                    self.write_keyword("REFERENCES");
8767                    self.write_space();
8768                    self.generate_table(&refs.table)?;
8769                    if !refs.columns.is_empty() {
8770                        if self.config.pretty {
8771                            self.write(" (");
8772                            self.write_newline();
8773                            self.indent_level += 1;
8774                            for (i, col) in refs.columns.iter().enumerate() {
8775                                if i > 0 {
8776                                    self.write(",");
8777                                    self.write_newline();
8778                                }
8779                                self.write_indent();
8780                                self.generate_identifier(col)?;
8781                            }
8782                            self.indent_level -= 1;
8783                            self.write_newline();
8784                            self.write_indent();
8785                            self.write(")");
8786                        } else {
8787                            self.write(" (");
8788                            for (i, col) in refs.columns.iter().enumerate() {
8789                                if i > 0 {
8790                                    self.write(", ");
8791                                }
8792                                self.generate_identifier(col)?;
8793                            }
8794                            self.write(")");
8795                        }
8796                    }
8797                    self.generate_referential_actions(refs)?;
8798                } else {
8799                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8800                    if let Some(ref action) = on_delete {
8801                        self.write_space();
8802                        self.write_keyword("ON DELETE");
8803                        self.write_space();
8804                        self.generate_referential_action(action);
8805                    }
8806                    if let Some(ref action) = on_update {
8807                        self.write_space();
8808                        self.write_keyword("ON UPDATE");
8809                        self.write_space();
8810                        self.generate_referential_action(action);
8811                    }
8812                }
8813                self.generate_constraint_modifiers(modifiers);
8814            }
8815            TableConstraint::Check {
8816                name,
8817                expression,
8818                modifiers,
8819            } => {
8820                if let Some(ref n) = name {
8821                    self.write_keyword("CONSTRAINT");
8822                    self.write_space();
8823                    self.generate_identifier(n)?;
8824                    self.write_space();
8825                }
8826                self.write_keyword("CHECK");
8827                self.write(" (");
8828                self.generate_expression(expression)?;
8829                self.write(")");
8830                self.generate_constraint_modifiers(modifiers);
8831            }
8832            TableConstraint::Index {
8833                name,
8834                columns,
8835                kind,
8836                modifiers,
8837                use_key_keyword,
8838                expression,
8839                index_type,
8840                granularity,
8841            } => {
8842                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8843                if expression.is_some() {
8844                    self.write_keyword("INDEX");
8845                    if let Some(ref n) = name {
8846                        self.write_space();
8847                        self.generate_identifier(n)?;
8848                    }
8849                    if let Some(ref expr) = expression {
8850                        self.write_space();
8851                        self.generate_expression(expr)?;
8852                    }
8853                    if let Some(ref idx_type) = index_type {
8854                        self.write_space();
8855                        self.write_keyword("TYPE");
8856                        self.write_space();
8857                        self.generate_expression(idx_type)?;
8858                    }
8859                    if let Some(ref gran) = granularity {
8860                        self.write_space();
8861                        self.write_keyword("GRANULARITY");
8862                        self.write_space();
8863                        self.generate_expression(gran)?;
8864                    }
8865                } else {
8866                    // Standard INDEX syntax
8867                    // Determine the index keyword to use
8868                    // MySQL normalizes KEY to INDEX
8869                    use crate::dialects::DialectType;
8870                    let index_keyword = if *use_key_keyword
8871                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8872                    {
8873                        "KEY"
8874                    } else {
8875                        "INDEX"
8876                    };
8877
8878                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8879                    if let Some(ref k) = kind {
8880                        self.write_keyword(k);
8881                        // For UNIQUE, don't add INDEX/KEY keyword
8882                        if k != "UNIQUE" {
8883                            self.write_space();
8884                            self.write_keyword(index_keyword);
8885                        }
8886                    } else {
8887                        self.write_keyword(index_keyword);
8888                    }
8889
8890                    // Output USING before name if using_before_columns is true and there's no name
8891                    if modifiers.using_before_columns && name.is_none() {
8892                        if let Some(ref using) = modifiers.using {
8893                            self.write_space();
8894                            self.write_keyword("USING");
8895                            self.write_space();
8896                            self.write_keyword(using);
8897                        }
8898                    }
8899
8900                    // Output index name if present
8901                    if let Some(ref n) = name {
8902                        self.write_space();
8903                        self.generate_identifier(n)?;
8904                    }
8905
8906                    // Output USING after name but before columns if using_before_columns and there's a name
8907                    if modifiers.using_before_columns && name.is_some() {
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 columns
8917                    self.write(" (");
8918                    for (i, col) in columns.iter().enumerate() {
8919                        if i > 0 {
8920                            self.write(", ");
8921                        }
8922                        self.generate_identifier(col)?;
8923                    }
8924                    self.write(")");
8925
8926                    // Output USING after columns if not using_before_columns
8927                    if !modifiers.using_before_columns {
8928                        if let Some(ref using) = modifiers.using {
8929                            self.write_space();
8930                            self.write_keyword("USING");
8931                            self.write_space();
8932                            self.write_keyword(using);
8933                        }
8934                    }
8935
8936                    // Output other constraint modifiers (but skip USING since we already handled it)
8937                    self.generate_constraint_modifiers_without_using(modifiers);
8938                }
8939            }
8940            TableConstraint::Projection { name, expression } => {
8941                // ClickHouse: PROJECTION name (SELECT ...)
8942                self.write_keyword("PROJECTION");
8943                self.write_space();
8944                self.generate_identifier(name)?;
8945                self.write(" (");
8946                self.generate_expression(expression)?;
8947                self.write(")");
8948            }
8949            TableConstraint::Like { source, options } => {
8950                self.write_keyword("LIKE");
8951                self.write_space();
8952                self.generate_table(source)?;
8953                for (action, prop) in options {
8954                    self.write_space();
8955                    match action {
8956                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8957                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8958                    }
8959                    self.write_space();
8960                    self.write_keyword(prop);
8961                }
8962            }
8963            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8964                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8965                self.write(" (");
8966                self.generate_identifier(start_col)?;
8967                self.write(", ");
8968                self.generate_identifier(end_col)?;
8969                self.write(")");
8970            }
8971            TableConstraint::Exclude {
8972                name,
8973                using,
8974                elements,
8975                include_columns,
8976                where_clause,
8977                with_params,
8978                using_index_tablespace,
8979                modifiers: _,
8980            } => {
8981                if let Some(ref n) = name {
8982                    self.write_keyword("CONSTRAINT");
8983                    self.write_space();
8984                    self.generate_identifier(n)?;
8985                    self.write_space();
8986                }
8987                self.write_keyword("EXCLUDE");
8988                if let Some(ref method) = using {
8989                    self.write_space();
8990                    self.write_keyword("USING");
8991                    self.write_space();
8992                    self.write(method);
8993                    self.write("(");
8994                } else {
8995                    self.write(" (");
8996                }
8997                for (i, elem) in elements.iter().enumerate() {
8998                    if i > 0 {
8999                        self.write(", ");
9000                    }
9001                    self.write(&elem.expression);
9002                    self.write_space();
9003                    self.write_keyword("WITH");
9004                    self.write_space();
9005                    self.write(&elem.operator);
9006                }
9007                self.write(")");
9008                if !include_columns.is_empty() {
9009                    self.write_space();
9010                    self.write_keyword("INCLUDE");
9011                    self.write(" (");
9012                    for (i, col) in include_columns.iter().enumerate() {
9013                        if i > 0 {
9014                            self.write(", ");
9015                        }
9016                        self.generate_identifier(col)?;
9017                    }
9018                    self.write(")");
9019                }
9020                if !with_params.is_empty() {
9021                    self.write_space();
9022                    self.write_keyword("WITH");
9023                    self.write(" (");
9024                    for (i, (key, val)) in with_params.iter().enumerate() {
9025                        if i > 0 {
9026                            self.write(", ");
9027                        }
9028                        self.write(key);
9029                        self.write("=");
9030                        self.write(val);
9031                    }
9032                    self.write(")");
9033                }
9034                if let Some(ref tablespace) = using_index_tablespace {
9035                    self.write_space();
9036                    self.write_keyword("USING INDEX TABLESPACE");
9037                    self.write_space();
9038                    self.write(tablespace);
9039                }
9040                if let Some(ref where_expr) = where_clause {
9041                    self.write_space();
9042                    self.write_keyword("WHERE");
9043                    self.write(" (");
9044                    self.generate_expression(where_expr)?;
9045                    self.write(")");
9046                }
9047            }
9048            TableConstraint::Tags(tags) => {
9049                self.write_keyword("TAG");
9050                self.write(" (");
9051                for (i, expr) in tags.expressions.iter().enumerate() {
9052                    if i > 0 {
9053                        self.write(", ");
9054                    }
9055                    self.generate_expression(expr)?;
9056                }
9057                self.write(")");
9058            }
9059            TableConstraint::InitiallyDeferred { deferred } => {
9060                self.write_keyword("INITIALLY");
9061                self.write_space();
9062                if *deferred {
9063                    self.write_keyword("DEFERRED");
9064                } else {
9065                    self.write_keyword("IMMEDIATE");
9066                }
9067            }
9068        }
9069        Ok(())
9070    }
9071
9072    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9073        // Output USING BTREE/HASH (MySQL) - comes first
9074        if let Some(using) = &modifiers.using {
9075            self.write_space();
9076            self.write_keyword("USING");
9077            self.write_space();
9078            self.write_keyword(using);
9079        }
9080        // Output ENFORCED/NOT ENFORCED
9081        if let Some(enforced) = modifiers.enforced {
9082            self.write_space();
9083            if enforced {
9084                self.write_keyword("ENFORCED");
9085            } else {
9086                self.write_keyword("NOT ENFORCED");
9087            }
9088        }
9089        // Output DEFERRABLE/NOT DEFERRABLE
9090        if let Some(deferrable) = modifiers.deferrable {
9091            self.write_space();
9092            if deferrable {
9093                self.write_keyword("DEFERRABLE");
9094            } else {
9095                self.write_keyword("NOT DEFERRABLE");
9096            }
9097        }
9098        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9099        if let Some(initially_deferred) = modifiers.initially_deferred {
9100            self.write_space();
9101            if initially_deferred {
9102                self.write_keyword("INITIALLY DEFERRED");
9103            } else {
9104                self.write_keyword("INITIALLY IMMEDIATE");
9105            }
9106        }
9107        // Output NORELY
9108        if modifiers.norely {
9109            self.write_space();
9110            self.write_keyword("NORELY");
9111        }
9112        // Output RELY
9113        if modifiers.rely {
9114            self.write_space();
9115            self.write_keyword("RELY");
9116        }
9117        // Output NOT VALID (PostgreSQL)
9118        if modifiers.not_valid {
9119            self.write_space();
9120            self.write_keyword("NOT VALID");
9121        }
9122        // Output ON CONFLICT (SQLite)
9123        if let Some(on_conflict) = &modifiers.on_conflict {
9124            self.write_space();
9125            self.write_keyword("ON CONFLICT");
9126            self.write_space();
9127            self.write_keyword(on_conflict);
9128        }
9129        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9130        if !modifiers.with_options.is_empty() {
9131            self.write_space();
9132            self.write_keyword("WITH");
9133            self.write(" (");
9134            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9135                if i > 0 {
9136                    self.write(", ");
9137                }
9138                self.write(key);
9139                self.write("=");
9140                self.write(value);
9141            }
9142            self.write(")");
9143        }
9144        // Output TSQL ON filegroup
9145        if let Some(ref fg) = modifiers.on_filegroup {
9146            self.write_space();
9147            self.write_keyword("ON");
9148            self.write_space();
9149            let _ = self.generate_identifier(fg);
9150        }
9151    }
9152
9153    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9154    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9155        // Output ENFORCED/NOT ENFORCED
9156        if let Some(enforced) = modifiers.enforced {
9157            self.write_space();
9158            if enforced {
9159                self.write_keyword("ENFORCED");
9160            } else {
9161                self.write_keyword("NOT ENFORCED");
9162            }
9163        }
9164        // Output DEFERRABLE/NOT DEFERRABLE
9165        if let Some(deferrable) = modifiers.deferrable {
9166            self.write_space();
9167            if deferrable {
9168                self.write_keyword("DEFERRABLE");
9169            } else {
9170                self.write_keyword("NOT DEFERRABLE");
9171            }
9172        }
9173        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9174        if let Some(initially_deferred) = modifiers.initially_deferred {
9175            self.write_space();
9176            if initially_deferred {
9177                self.write_keyword("INITIALLY DEFERRED");
9178            } else {
9179                self.write_keyword("INITIALLY IMMEDIATE");
9180            }
9181        }
9182        // Output NORELY
9183        if modifiers.norely {
9184            self.write_space();
9185            self.write_keyword("NORELY");
9186        }
9187        // Output RELY
9188        if modifiers.rely {
9189            self.write_space();
9190            self.write_keyword("RELY");
9191        }
9192        // Output NOT VALID (PostgreSQL)
9193        if modifiers.not_valid {
9194            self.write_space();
9195            self.write_keyword("NOT VALID");
9196        }
9197        // Output ON CONFLICT (SQLite)
9198        if let Some(on_conflict) = &modifiers.on_conflict {
9199            self.write_space();
9200            self.write_keyword("ON CONFLICT");
9201            self.write_space();
9202            self.write_keyword(on_conflict);
9203        }
9204        // Output MySQL index-specific modifiers
9205        self.generate_index_specific_modifiers(modifiers);
9206    }
9207
9208    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9209    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9210        if let Some(ref comment) = modifiers.comment {
9211            self.write_space();
9212            self.write_keyword("COMMENT");
9213            self.write(" '");
9214            self.write(comment);
9215            self.write("'");
9216        }
9217        if let Some(visible) = modifiers.visible {
9218            self.write_space();
9219            if visible {
9220                self.write_keyword("VISIBLE");
9221            } else {
9222                self.write_keyword("INVISIBLE");
9223            }
9224        }
9225        if let Some(ref attr) = modifiers.engine_attribute {
9226            self.write_space();
9227            self.write_keyword("ENGINE_ATTRIBUTE");
9228            self.write(" = '");
9229            self.write(attr);
9230            self.write("'");
9231        }
9232        if let Some(ref parser) = modifiers.with_parser {
9233            self.write_space();
9234            self.write_keyword("WITH PARSER");
9235            self.write_space();
9236            self.write(parser);
9237        }
9238    }
9239
9240    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9241        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9242        if !fk_ref.match_after_actions {
9243            if let Some(ref match_type) = fk_ref.match_type {
9244                self.write_space();
9245                self.write_keyword("MATCH");
9246                self.write_space();
9247                match match_type {
9248                    MatchType::Full => self.write_keyword("FULL"),
9249                    MatchType::Partial => self.write_keyword("PARTIAL"),
9250                    MatchType::Simple => self.write_keyword("SIMPLE"),
9251                }
9252            }
9253        }
9254
9255        // Output ON UPDATE and ON DELETE in the original order
9256        if fk_ref.on_update_first {
9257            if let Some(ref action) = fk_ref.on_update {
9258                self.write_space();
9259                self.write_keyword("ON UPDATE");
9260                self.write_space();
9261                self.generate_referential_action(action);
9262            }
9263            if let Some(ref action) = fk_ref.on_delete {
9264                self.write_space();
9265                self.write_keyword("ON DELETE");
9266                self.write_space();
9267                self.generate_referential_action(action);
9268            }
9269        } else {
9270            if let Some(ref action) = fk_ref.on_delete {
9271                self.write_space();
9272                self.write_keyword("ON DELETE");
9273                self.write_space();
9274                self.generate_referential_action(action);
9275            }
9276            if let Some(ref action) = fk_ref.on_update {
9277                self.write_space();
9278                self.write_keyword("ON UPDATE");
9279                self.write_space();
9280                self.generate_referential_action(action);
9281            }
9282        }
9283
9284        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9285        if fk_ref.match_after_actions {
9286            if let Some(ref match_type) = fk_ref.match_type {
9287                self.write_space();
9288                self.write_keyword("MATCH");
9289                self.write_space();
9290                match match_type {
9291                    MatchType::Full => self.write_keyword("FULL"),
9292                    MatchType::Partial => self.write_keyword("PARTIAL"),
9293                    MatchType::Simple => self.write_keyword("SIMPLE"),
9294                }
9295            }
9296        }
9297
9298        // DEFERRABLE / NOT DEFERRABLE
9299        if let Some(deferrable) = fk_ref.deferrable {
9300            self.write_space();
9301            if deferrable {
9302                self.write_keyword("DEFERRABLE");
9303            } else {
9304                self.write_keyword("NOT DEFERRABLE");
9305            }
9306        }
9307
9308        Ok(())
9309    }
9310
9311    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9312        match action {
9313            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9314            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9315            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9316            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9317            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9318        }
9319    }
9320
9321    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9322        // Athena: DROP TABLE uses Hive engine (backticks)
9323        let saved_athena_hive_context = self.athena_hive_context;
9324        if matches!(
9325            self.config.dialect,
9326            Some(crate::dialects::DialectType::Athena)
9327        ) {
9328            self.athena_hive_context = true;
9329        }
9330
9331        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9332        for comment in &dt.leading_comments {
9333            self.write_formatted_comment(comment);
9334            self.write_space();
9335        }
9336        self.write_keyword("DROP TABLE");
9337
9338        if dt.if_exists {
9339            self.write_space();
9340            self.write_keyword("IF EXISTS");
9341        }
9342
9343        self.write_space();
9344        for (i, table) in dt.names.iter().enumerate() {
9345            if i > 0 {
9346                self.write(", ");
9347            }
9348            self.generate_table(table)?;
9349        }
9350
9351        if dt.cascade_constraints {
9352            self.write_space();
9353            self.write_keyword("CASCADE CONSTRAINTS");
9354        } else if dt.cascade {
9355            self.write_space();
9356            self.write_keyword("CASCADE");
9357        }
9358
9359        if dt.purge {
9360            self.write_space();
9361            self.write_keyword("PURGE");
9362        }
9363
9364        // Restore Athena Hive context
9365        self.athena_hive_context = saved_athena_hive_context;
9366
9367        Ok(())
9368    }
9369
9370    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9371        // Athena: ALTER TABLE uses Hive engine (backticks)
9372        let saved_athena_hive_context = self.athena_hive_context;
9373        if matches!(
9374            self.config.dialect,
9375            Some(crate::dialects::DialectType::Athena)
9376        ) {
9377            self.athena_hive_context = true;
9378        }
9379
9380        self.write_keyword("ALTER TABLE");
9381        if at.if_exists {
9382            self.write_space();
9383            self.write_keyword("IF EXISTS");
9384        }
9385        self.write_space();
9386        self.generate_table(&at.name)?;
9387
9388        // ClickHouse: ON CLUSTER clause
9389        if let Some(ref on_cluster) = at.on_cluster {
9390            self.write_space();
9391            self.generate_on_cluster(on_cluster)?;
9392        }
9393
9394        // Hive: PARTITION(key=value, ...) clause
9395        if let Some(ref partition) = at.partition {
9396            self.write_space();
9397            self.write_keyword("PARTITION");
9398            self.write("(");
9399            for (i, (key, value)) in partition.iter().enumerate() {
9400                if i > 0 {
9401                    self.write(", ");
9402                }
9403                self.generate_identifier(key)?;
9404                self.write(" = ");
9405                self.generate_expression(value)?;
9406            }
9407            self.write(")");
9408        }
9409
9410        // TSQL: WITH CHECK / WITH NOCHECK modifier
9411        if let Some(ref with_check) = at.with_check {
9412            self.write_space();
9413            self.write_keyword(with_check);
9414        }
9415
9416        if self.config.pretty {
9417            // In pretty mode, format actions with newlines and indentation
9418            self.write_newline();
9419            self.indent_level += 1;
9420            for (i, action) in at.actions.iter().enumerate() {
9421                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9422                let is_continuation = i > 0
9423                    && matches!(
9424                        (&at.actions[i - 1], action),
9425                        (
9426                            AlterTableAction::AddColumn { .. },
9427                            AlterTableAction::AddColumn { .. }
9428                        ) | (
9429                            AlterTableAction::AddConstraint(_),
9430                            AlterTableAction::AddConstraint(_)
9431                        )
9432                    );
9433                if i > 0 {
9434                    self.write(",");
9435                    self.write_newline();
9436                }
9437                self.write_indent();
9438                self.generate_alter_action_with_continuation(action, is_continuation)?;
9439            }
9440            self.indent_level -= 1;
9441        } else {
9442            for (i, action) in at.actions.iter().enumerate() {
9443                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9444                let is_continuation = i > 0
9445                    && matches!(
9446                        (&at.actions[i - 1], action),
9447                        (
9448                            AlterTableAction::AddColumn { .. },
9449                            AlterTableAction::AddColumn { .. }
9450                        ) | (
9451                            AlterTableAction::AddConstraint(_),
9452                            AlterTableAction::AddConstraint(_)
9453                        )
9454                    );
9455                if i > 0 {
9456                    self.write(",");
9457                }
9458                self.write_space();
9459                self.generate_alter_action_with_continuation(action, is_continuation)?;
9460            }
9461        }
9462
9463        // MySQL ALTER TABLE trailing options
9464        if let Some(ref algorithm) = at.algorithm {
9465            self.write(", ");
9466            self.write_keyword("ALGORITHM");
9467            self.write("=");
9468            self.write_keyword(algorithm);
9469        }
9470        if let Some(ref lock) = at.lock {
9471            self.write(", ");
9472            self.write_keyword("LOCK");
9473            self.write("=");
9474            self.write_keyword(lock);
9475        }
9476
9477        // Restore Athena Hive context
9478        self.athena_hive_context = saved_athena_hive_context;
9479
9480        Ok(())
9481    }
9482
9483    fn generate_alter_action_with_continuation(
9484        &mut self,
9485        action: &AlterTableAction,
9486        is_continuation: bool,
9487    ) -> Result<()> {
9488        match action {
9489            AlterTableAction::AddColumn {
9490                column,
9491                if_not_exists,
9492                position,
9493            } => {
9494                use crate::dialects::DialectType;
9495                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9496                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9497                // For other dialects, repeat ADD COLUMN for each
9498                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9499                let is_tsql_like = matches!(
9500                    self.config.dialect,
9501                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9502                );
9503                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9504                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9505
9506                if is_continuation && (is_snowflake || is_tsql_like) {
9507                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9508                } else if is_snowflake {
9509                    self.write_keyword("ADD");
9510                    self.write_space();
9511                } else if is_athena {
9512                    // Athena uses ADD COLUMNS (col_def) syntax
9513                    self.write_keyword("ADD COLUMNS");
9514                    self.write(" (");
9515                } else if self.config.alter_table_include_column_keyword {
9516                    self.write_keyword("ADD COLUMN");
9517                    self.write_space();
9518                } else {
9519                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9520                    self.write_keyword("ADD");
9521                    self.write_space();
9522                }
9523
9524                if *if_not_exists {
9525                    self.write_keyword("IF NOT EXISTS");
9526                    self.write_space();
9527                }
9528                self.generate_column_def(column)?;
9529
9530                // Close parenthesis for Athena
9531                if is_athena {
9532                    self.write(")");
9533                }
9534
9535                // Column position (FIRST or AFTER)
9536                if let Some(pos) = position {
9537                    self.write_space();
9538                    match pos {
9539                        ColumnPosition::First => self.write_keyword("FIRST"),
9540                        ColumnPosition::After(col_name) => {
9541                            self.write_keyword("AFTER");
9542                            self.write_space();
9543                            self.generate_identifier(col_name)?;
9544                        }
9545                    }
9546                }
9547            }
9548            AlterTableAction::DropColumn {
9549                name,
9550                if_exists,
9551                cascade,
9552            } => {
9553                self.write_keyword("DROP COLUMN");
9554                if *if_exists {
9555                    self.write_space();
9556                    self.write_keyword("IF EXISTS");
9557                }
9558                self.write_space();
9559                self.generate_identifier(name)?;
9560                if *cascade {
9561                    self.write_space();
9562                    self.write_keyword("CASCADE");
9563                }
9564            }
9565            AlterTableAction::DropColumns { names } => {
9566                self.write_keyword("DROP COLUMNS");
9567                self.write(" (");
9568                for (i, name) in names.iter().enumerate() {
9569                    if i > 0 {
9570                        self.write(", ");
9571                    }
9572                    self.generate_identifier(name)?;
9573                }
9574                self.write(")");
9575            }
9576            AlterTableAction::RenameColumn {
9577                old_name,
9578                new_name,
9579                if_exists,
9580            } => {
9581                self.write_keyword("RENAME COLUMN");
9582                if *if_exists {
9583                    self.write_space();
9584                    self.write_keyword("IF EXISTS");
9585                }
9586                self.write_space();
9587                self.generate_identifier(old_name)?;
9588                self.write_space();
9589                self.write_keyword("TO");
9590                self.write_space();
9591                self.generate_identifier(new_name)?;
9592            }
9593            AlterTableAction::AlterColumn {
9594                name,
9595                action,
9596                use_modify_keyword,
9597            } => {
9598                use crate::dialects::DialectType;
9599                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9600                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9601                let use_modify = *use_modify_keyword
9602                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9603                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9604                if use_modify {
9605                    self.write_keyword("MODIFY COLUMN");
9606                    self.write_space();
9607                    self.generate_identifier(name)?;
9608                    // For MODIFY COLUMN, output the type directly
9609                    if let AlterColumnAction::SetDataType {
9610                        data_type,
9611                        using: _,
9612                        collate,
9613                    } = action
9614                    {
9615                        self.write_space();
9616                        self.generate_data_type(data_type)?;
9617                        // Output COLLATE clause if present
9618                        if let Some(collate_name) = collate {
9619                            self.write_space();
9620                            self.write_keyword("COLLATE");
9621                            self.write_space();
9622                            // Output as single-quoted string
9623                            self.write(&format!("'{}'", collate_name));
9624                        }
9625                    } else {
9626                        self.write_space();
9627                        self.generate_alter_column_action(action)?;
9628                    }
9629                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9630                    && matches!(action, AlterColumnAction::SetDataType { .. })
9631                {
9632                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9633                    self.write_keyword("CHANGE COLUMN");
9634                    self.write_space();
9635                    self.generate_identifier(name)?;
9636                    self.write_space();
9637                    self.generate_identifier(name)?;
9638                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9639                        self.write_space();
9640                        self.generate_data_type(data_type)?;
9641                    }
9642                } else {
9643                    self.write_keyword("ALTER COLUMN");
9644                    self.write_space();
9645                    self.generate_identifier(name)?;
9646                    self.write_space();
9647                    self.generate_alter_column_action(action)?;
9648                }
9649            }
9650            AlterTableAction::RenameTable(new_name) => {
9651                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9652                let mysql_like = matches!(
9653                    self.config.dialect,
9654                    Some(DialectType::MySQL)
9655                        | Some(DialectType::Doris)
9656                        | Some(DialectType::StarRocks)
9657                        | Some(DialectType::SingleStore)
9658                );
9659                if mysql_like {
9660                    self.write_keyword("RENAME");
9661                } else {
9662                    self.write_keyword("RENAME TO");
9663                }
9664                self.write_space();
9665                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9666                let rename_table_with_db = !matches!(
9667                    self.config.dialect,
9668                    Some(DialectType::Doris)
9669                        | Some(DialectType::DuckDB)
9670                        | Some(DialectType::BigQuery)
9671                        | Some(DialectType::PostgreSQL)
9672                );
9673                if !rename_table_with_db {
9674                    let mut stripped = new_name.clone();
9675                    stripped.schema = None;
9676                    stripped.catalog = None;
9677                    self.generate_table(&stripped)?;
9678                } else {
9679                    self.generate_table(new_name)?;
9680                }
9681            }
9682            AlterTableAction::AddConstraint(constraint) => {
9683                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9684                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9685                if !is_continuation {
9686                    self.write_keyword("ADD");
9687                    self.write_space();
9688                }
9689                self.generate_table_constraint(constraint)?;
9690            }
9691            AlterTableAction::DropConstraint { name, if_exists } => {
9692                self.write_keyword("DROP CONSTRAINT");
9693                if *if_exists {
9694                    self.write_space();
9695                    self.write_keyword("IF EXISTS");
9696                }
9697                self.write_space();
9698                self.generate_identifier(name)?;
9699            }
9700            AlterTableAction::DropForeignKey { name } => {
9701                self.write_keyword("DROP FOREIGN KEY");
9702                self.write_space();
9703                self.generate_identifier(name)?;
9704            }
9705            AlterTableAction::DropPartition {
9706                partitions,
9707                if_exists,
9708            } => {
9709                self.write_keyword("DROP");
9710                if *if_exists {
9711                    self.write_space();
9712                    self.write_keyword("IF EXISTS");
9713                }
9714                for (i, partition) in partitions.iter().enumerate() {
9715                    if i > 0 {
9716                        self.write(",");
9717                    }
9718                    self.write_space();
9719                    self.write_keyword("PARTITION");
9720                    // Check for special ClickHouse partition formats
9721                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9722                        // ClickHouse: PARTITION <expression>
9723                        self.write_space();
9724                        self.generate_expression(&partition[0].1)?;
9725                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9726                        // ClickHouse: PARTITION ALL
9727                        self.write_space();
9728                        self.write_keyword("ALL");
9729                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9730                        // ClickHouse: PARTITION ID 'string'
9731                        self.write_space();
9732                        self.write_keyword("ID");
9733                        self.write_space();
9734                        self.generate_expression(&partition[0].1)?;
9735                    } else {
9736                        // Standard SQL: PARTITION(key=value, ...)
9737                        self.write("(");
9738                        for (j, (key, value)) in partition.iter().enumerate() {
9739                            if j > 0 {
9740                                self.write(", ");
9741                            }
9742                            self.generate_identifier(key)?;
9743                            self.write(" = ");
9744                            self.generate_expression(value)?;
9745                        }
9746                        self.write(")");
9747                    }
9748                }
9749            }
9750            AlterTableAction::Delete { where_clause } => {
9751                self.write_keyword("DELETE");
9752                self.write_space();
9753                self.write_keyword("WHERE");
9754                self.write_space();
9755                self.generate_expression(where_clause)?;
9756            }
9757            AlterTableAction::SwapWith(target) => {
9758                self.write_keyword("SWAP WITH");
9759                self.write_space();
9760                self.generate_table(target)?;
9761            }
9762            AlterTableAction::SetProperty { properties } => {
9763                use crate::dialects::DialectType;
9764                self.write_keyword("SET");
9765                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9766                let is_trino_presto = matches!(
9767                    self.config.dialect,
9768                    Some(DialectType::Trino) | Some(DialectType::Presto)
9769                );
9770                if is_trino_presto {
9771                    self.write_space();
9772                    self.write_keyword("PROPERTIES");
9773                }
9774                let eq = if is_trino_presto { " = " } else { "=" };
9775                for (i, (key, value)) in properties.iter().enumerate() {
9776                    if i > 0 {
9777                        self.write(",");
9778                    }
9779                    self.write_space();
9780                    // Handle quoted property names for Trino
9781                    if key.contains(' ') {
9782                        self.generate_string_literal(key)?;
9783                    } else {
9784                        self.write(key);
9785                    }
9786                    self.write(eq);
9787                    self.generate_expression(value)?;
9788                }
9789            }
9790            AlterTableAction::UnsetProperty { properties } => {
9791                self.write_keyword("UNSET");
9792                for (i, name) in properties.iter().enumerate() {
9793                    if i > 0 {
9794                        self.write(",");
9795                    }
9796                    self.write_space();
9797                    self.write(name);
9798                }
9799            }
9800            AlterTableAction::ClusterBy { expressions } => {
9801                self.write_keyword("CLUSTER BY");
9802                self.write(" (");
9803                for (i, expr) in expressions.iter().enumerate() {
9804                    if i > 0 {
9805                        self.write(", ");
9806                    }
9807                    self.generate_expression(expr)?;
9808                }
9809                self.write(")");
9810            }
9811            AlterTableAction::SetTag { expressions } => {
9812                self.write_keyword("SET TAG");
9813                for (i, (key, value)) in expressions.iter().enumerate() {
9814                    if i > 0 {
9815                        self.write(",");
9816                    }
9817                    self.write_space();
9818                    self.write(key);
9819                    self.write(" = ");
9820                    self.generate_expression(value)?;
9821                }
9822            }
9823            AlterTableAction::UnsetTag { names } => {
9824                self.write_keyword("UNSET TAG");
9825                for (i, name) in names.iter().enumerate() {
9826                    if i > 0 {
9827                        self.write(",");
9828                    }
9829                    self.write_space();
9830                    self.write(name);
9831                }
9832            }
9833            AlterTableAction::SetOptions { expressions } => {
9834                self.write_keyword("SET");
9835                self.write(" (");
9836                for (i, expr) in expressions.iter().enumerate() {
9837                    if i > 0 {
9838                        self.write(", ");
9839                    }
9840                    self.generate_expression(expr)?;
9841                }
9842                self.write(")");
9843            }
9844            AlterTableAction::AlterIndex { name, visible } => {
9845                self.write_keyword("ALTER INDEX");
9846                self.write_space();
9847                self.generate_identifier(name)?;
9848                self.write_space();
9849                if *visible {
9850                    self.write_keyword("VISIBLE");
9851                } else {
9852                    self.write_keyword("INVISIBLE");
9853                }
9854            }
9855            AlterTableAction::SetAttribute { attribute } => {
9856                self.write_keyword("SET");
9857                self.write_space();
9858                self.write_keyword(attribute);
9859            }
9860            AlterTableAction::SetStageFileFormat { options } => {
9861                self.write_keyword("SET");
9862                self.write_space();
9863                self.write_keyword("STAGE_FILE_FORMAT");
9864                self.write(" = (");
9865                if let Some(opts) = options {
9866                    self.generate_space_separated_properties(opts)?;
9867                }
9868                self.write(")");
9869            }
9870            AlterTableAction::SetStageCopyOptions { options } => {
9871                self.write_keyword("SET");
9872                self.write_space();
9873                self.write_keyword("STAGE_COPY_OPTIONS");
9874                self.write(" = (");
9875                if let Some(opts) = options {
9876                    self.generate_space_separated_properties(opts)?;
9877                }
9878                self.write(")");
9879            }
9880            AlterTableAction::AddColumns { columns, cascade } => {
9881                // Oracle uses ADD (...) without COLUMNS keyword
9882                // Hive/Spark uses ADD COLUMNS (...)
9883                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9884                if is_oracle {
9885                    self.write_keyword("ADD");
9886                } else {
9887                    self.write_keyword("ADD COLUMNS");
9888                }
9889                self.write(" (");
9890                for (i, col) in columns.iter().enumerate() {
9891                    if i > 0 {
9892                        self.write(", ");
9893                    }
9894                    self.generate_column_def(col)?;
9895                }
9896                self.write(")");
9897                if *cascade {
9898                    self.write_space();
9899                    self.write_keyword("CASCADE");
9900                }
9901            }
9902            AlterTableAction::ChangeColumn {
9903                old_name,
9904                new_name,
9905                data_type,
9906                comment,
9907                cascade,
9908            } => {
9909                use crate::dialects::DialectType;
9910                let is_spark = matches!(
9911                    self.config.dialect,
9912                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9913                );
9914                let is_rename = old_name.name != new_name.name;
9915
9916                if is_spark {
9917                    if is_rename {
9918                        // Spark: RENAME COLUMN old TO new
9919                        self.write_keyword("RENAME COLUMN");
9920                        self.write_space();
9921                        self.generate_identifier(old_name)?;
9922                        self.write_space();
9923                        self.write_keyword("TO");
9924                        self.write_space();
9925                        self.generate_identifier(new_name)?;
9926                    } else if comment.is_some() {
9927                        // Spark: ALTER COLUMN old COMMENT 'comment'
9928                        self.write_keyword("ALTER COLUMN");
9929                        self.write_space();
9930                        self.generate_identifier(old_name)?;
9931                        self.write_space();
9932                        self.write_keyword("COMMENT");
9933                        self.write_space();
9934                        self.write("'");
9935                        self.write(comment.as_ref().unwrap());
9936                        self.write("'");
9937                    } else if data_type.is_some() {
9938                        // Spark: ALTER COLUMN old TYPE data_type
9939                        self.write_keyword("ALTER COLUMN");
9940                        self.write_space();
9941                        self.generate_identifier(old_name)?;
9942                        self.write_space();
9943                        self.write_keyword("TYPE");
9944                        self.write_space();
9945                        self.generate_data_type(data_type.as_ref().unwrap())?;
9946                    } else {
9947                        // Fallback to CHANGE COLUMN
9948                        self.write_keyword("CHANGE COLUMN");
9949                        self.write_space();
9950                        self.generate_identifier(old_name)?;
9951                        self.write_space();
9952                        self.generate_identifier(new_name)?;
9953                    }
9954                } else {
9955                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9956                    if data_type.is_some() {
9957                        self.write_keyword("CHANGE COLUMN");
9958                    } else {
9959                        self.write_keyword("CHANGE");
9960                    }
9961                    self.write_space();
9962                    self.generate_identifier(old_name)?;
9963                    self.write_space();
9964                    self.generate_identifier(new_name)?;
9965                    if let Some(ref dt) = data_type {
9966                        self.write_space();
9967                        self.generate_data_type(dt)?;
9968                    }
9969                    if let Some(ref c) = comment {
9970                        self.write_space();
9971                        self.write_keyword("COMMENT");
9972                        self.write_space();
9973                        self.write("'");
9974                        self.write(c);
9975                        self.write("'");
9976                    }
9977                    if *cascade {
9978                        self.write_space();
9979                        self.write_keyword("CASCADE");
9980                    }
9981                }
9982            }
9983            AlterTableAction::AddPartition {
9984                partition,
9985                if_not_exists,
9986                location,
9987            } => {
9988                self.write_keyword("ADD");
9989                self.write_space();
9990                if *if_not_exists {
9991                    self.write_keyword("IF NOT EXISTS");
9992                    self.write_space();
9993                }
9994                self.generate_expression(partition)?;
9995                if let Some(ref loc) = location {
9996                    self.write_space();
9997                    self.write_keyword("LOCATION");
9998                    self.write_space();
9999                    self.generate_expression(loc)?;
10000                }
10001            }
10002            AlterTableAction::AlterSortKey {
10003                this,
10004                expressions,
10005                compound,
10006            } => {
10007                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10008                self.write_keyword("ALTER");
10009                if *compound {
10010                    self.write_space();
10011                    self.write_keyword("COMPOUND");
10012                }
10013                self.write_space();
10014                self.write_keyword("SORTKEY");
10015                self.write_space();
10016                if let Some(style) = this {
10017                    self.write_keyword(style);
10018                } else if !expressions.is_empty() {
10019                    self.write("(");
10020                    for (i, expr) in expressions.iter().enumerate() {
10021                        if i > 0 {
10022                            self.write(", ");
10023                        }
10024                        self.generate_expression(expr)?;
10025                    }
10026                    self.write(")");
10027                }
10028            }
10029            AlterTableAction::AlterDistStyle { style, distkey } => {
10030                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10031                self.write_keyword("ALTER");
10032                self.write_space();
10033                self.write_keyword("DISTSTYLE");
10034                self.write_space();
10035                self.write_keyword(style);
10036                if let Some(col) = distkey {
10037                    self.write_space();
10038                    self.write_keyword("DISTKEY");
10039                    self.write_space();
10040                    self.generate_identifier(col)?;
10041                }
10042            }
10043            AlterTableAction::SetTableProperties { properties } => {
10044                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10045                self.write_keyword("SET TABLE PROPERTIES");
10046                self.write(" (");
10047                for (i, (key, value)) in properties.iter().enumerate() {
10048                    if i > 0 {
10049                        self.write(", ");
10050                    }
10051                    self.generate_expression(key)?;
10052                    self.write(" = ");
10053                    self.generate_expression(value)?;
10054                }
10055                self.write(")");
10056            }
10057            AlterTableAction::SetLocation { location } => {
10058                // Redshift: SET LOCATION 's3://bucket/folder/'
10059                self.write_keyword("SET LOCATION");
10060                self.write_space();
10061                self.write("'");
10062                self.write(location);
10063                self.write("'");
10064            }
10065            AlterTableAction::SetFileFormat { format } => {
10066                // Redshift: SET FILE FORMAT AVRO
10067                self.write_keyword("SET FILE FORMAT");
10068                self.write_space();
10069                self.write_keyword(format);
10070            }
10071            AlterTableAction::ReplacePartition { partition, source } => {
10072                // ClickHouse: REPLACE PARTITION expr FROM source
10073                self.write_keyword("REPLACE PARTITION");
10074                self.write_space();
10075                self.generate_expression(partition)?;
10076                if let Some(src) = source {
10077                    self.write_space();
10078                    self.write_keyword("FROM");
10079                    self.write_space();
10080                    self.generate_expression(src)?;
10081                }
10082            }
10083            AlterTableAction::Raw { sql } => {
10084                self.write(sql);
10085            }
10086        }
10087        Ok(())
10088    }
10089
10090    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10091        match action {
10092            AlterColumnAction::SetDataType {
10093                data_type,
10094                using,
10095                collate,
10096            } => {
10097                use crate::dialects::DialectType;
10098                // Dialect-specific type change syntax:
10099                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10100                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10101                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10102                let is_no_prefix = matches!(
10103                    self.config.dialect,
10104                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10105                );
10106                let is_type_only = matches!(
10107                    self.config.dialect,
10108                    Some(DialectType::Redshift)
10109                        | Some(DialectType::Spark)
10110                        | Some(DialectType::Databricks)
10111                );
10112                if is_type_only {
10113                    self.write_keyword("TYPE");
10114                    self.write_space();
10115                } else if !is_no_prefix {
10116                    self.write_keyword("SET DATA TYPE");
10117                    self.write_space();
10118                }
10119                self.generate_data_type(data_type)?;
10120                if let Some(ref collation) = collate {
10121                    self.write_space();
10122                    self.write_keyword("COLLATE");
10123                    self.write_space();
10124                    self.write(collation);
10125                }
10126                if let Some(ref using_expr) = using {
10127                    self.write_space();
10128                    self.write_keyword("USING");
10129                    self.write_space();
10130                    self.generate_expression(using_expr)?;
10131                }
10132            }
10133            AlterColumnAction::SetDefault(expr) => {
10134                self.write_keyword("SET DEFAULT");
10135                self.write_space();
10136                self.generate_expression(expr)?;
10137            }
10138            AlterColumnAction::DropDefault => {
10139                self.write_keyword("DROP DEFAULT");
10140            }
10141            AlterColumnAction::SetNotNull => {
10142                self.write_keyword("SET NOT NULL");
10143            }
10144            AlterColumnAction::DropNotNull => {
10145                self.write_keyword("DROP NOT NULL");
10146            }
10147            AlterColumnAction::Comment(comment) => {
10148                self.write_keyword("COMMENT");
10149                self.write_space();
10150                self.generate_string_literal(comment)?;
10151            }
10152            AlterColumnAction::SetVisible => {
10153                self.write_keyword("SET VISIBLE");
10154            }
10155            AlterColumnAction::SetInvisible => {
10156                self.write_keyword("SET INVISIBLE");
10157            }
10158        }
10159        Ok(())
10160    }
10161
10162    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10163        self.write_keyword("CREATE");
10164
10165        if ci.unique {
10166            self.write_space();
10167            self.write_keyword("UNIQUE");
10168        }
10169
10170        // TSQL CLUSTERED/NONCLUSTERED modifier
10171        if let Some(ref clustered) = ci.clustered {
10172            self.write_space();
10173            self.write_keyword(clustered);
10174        }
10175
10176        self.write_space();
10177        self.write_keyword("INDEX");
10178
10179        // PostgreSQL CONCURRENTLY modifier
10180        if ci.concurrently {
10181            self.write_space();
10182            self.write_keyword("CONCURRENTLY");
10183        }
10184
10185        if ci.if_not_exists {
10186            self.write_space();
10187            self.write_keyword("IF NOT EXISTS");
10188        }
10189
10190        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10191        if !ci.name.name.is_empty() {
10192            self.write_space();
10193            self.generate_identifier(&ci.name)?;
10194        }
10195        self.write_space();
10196        self.write_keyword("ON");
10197        // Hive uses ON TABLE
10198        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10199            self.write_space();
10200            self.write_keyword("TABLE");
10201        }
10202        self.write_space();
10203        self.generate_table(&ci.table)?;
10204
10205        // Column list (optional for COLUMNSTORE indexes)
10206        // Standard SQL convention: ON t(a) without space before paren
10207        if !ci.columns.is_empty() || ci.using.is_some() {
10208            let space_before_paren = false;
10209
10210            if let Some(ref using) = ci.using {
10211                self.write_space();
10212                self.write_keyword("USING");
10213                self.write_space();
10214                self.write(using);
10215                if space_before_paren {
10216                    self.write(" (");
10217                } else {
10218                    self.write("(");
10219                }
10220            } else {
10221                if space_before_paren {
10222                    self.write(" (");
10223                } else {
10224                    self.write("(");
10225                }
10226            }
10227            for (i, col) in ci.columns.iter().enumerate() {
10228                if i > 0 {
10229                    self.write(", ");
10230                }
10231                self.generate_identifier(&col.column)?;
10232                if let Some(ref opclass) = col.opclass {
10233                    self.write_space();
10234                    self.write(opclass);
10235                }
10236                if col.desc {
10237                    self.write_space();
10238                    self.write_keyword("DESC");
10239                } else if col.asc {
10240                    self.write_space();
10241                    self.write_keyword("ASC");
10242                }
10243                if let Some(nulls_first) = col.nulls_first {
10244                    self.write_space();
10245                    self.write_keyword("NULLS");
10246                    self.write_space();
10247                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10248                }
10249            }
10250            self.write(")");
10251        }
10252
10253        // PostgreSQL INCLUDE (col1, col2) clause
10254        if !ci.include_columns.is_empty() {
10255            self.write_space();
10256            self.write_keyword("INCLUDE");
10257            self.write(" (");
10258            for (i, col) in ci.include_columns.iter().enumerate() {
10259                if i > 0 {
10260                    self.write(", ");
10261                }
10262                self.generate_identifier(col)?;
10263            }
10264            self.write(")");
10265        }
10266
10267        // TSQL: WITH (option=value, ...) clause
10268        if !ci.with_options.is_empty() {
10269            self.write_space();
10270            self.write_keyword("WITH");
10271            self.write(" (");
10272            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10273                if i > 0 {
10274                    self.write(", ");
10275                }
10276                self.write(key);
10277                self.write("=");
10278                self.write(value);
10279            }
10280            self.write(")");
10281        }
10282
10283        // PostgreSQL WHERE clause for partial indexes
10284        if let Some(ref where_clause) = ci.where_clause {
10285            self.write_space();
10286            self.write_keyword("WHERE");
10287            self.write_space();
10288            self.generate_expression(where_clause)?;
10289        }
10290
10291        // TSQL: ON filegroup or partition scheme clause
10292        if let Some(ref on_fg) = ci.on_filegroup {
10293            self.write_space();
10294            self.write_keyword("ON");
10295            self.write_space();
10296            self.write(on_fg);
10297        }
10298
10299        Ok(())
10300    }
10301
10302    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10303        self.write_keyword("DROP INDEX");
10304
10305        if di.concurrently {
10306            self.write_space();
10307            self.write_keyword("CONCURRENTLY");
10308        }
10309
10310        if di.if_exists {
10311            self.write_space();
10312            self.write_keyword("IF EXISTS");
10313        }
10314
10315        self.write_space();
10316        self.generate_identifier(&di.name)?;
10317
10318        if let Some(ref table) = di.table {
10319            self.write_space();
10320            self.write_keyword("ON");
10321            self.write_space();
10322            self.generate_table(table)?;
10323        }
10324
10325        Ok(())
10326    }
10327
10328    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10329        self.write_keyword("CREATE");
10330
10331        // MySQL: ALGORITHM=...
10332        if let Some(ref algorithm) = cv.algorithm {
10333            self.write_space();
10334            self.write_keyword("ALGORITHM");
10335            self.write("=");
10336            self.write_keyword(algorithm);
10337        }
10338
10339        // MySQL: DEFINER=...
10340        if let Some(ref definer) = cv.definer {
10341            self.write_space();
10342            self.write_keyword("DEFINER");
10343            self.write("=");
10344            self.write(definer);
10345        }
10346
10347        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10348        if cv.security_sql_style {
10349            if let Some(ref security) = cv.security {
10350                self.write_space();
10351                self.write_keyword("SQL SECURITY");
10352                self.write_space();
10353                match security {
10354                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10355                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10356                    FunctionSecurity::None => self.write_keyword("NONE"),
10357                }
10358            }
10359        }
10360
10361        if cv.or_replace {
10362            self.write_space();
10363            self.write_keyword("OR REPLACE");
10364        }
10365
10366        if cv.temporary {
10367            self.write_space();
10368            self.write_keyword("TEMPORARY");
10369        }
10370
10371        if cv.materialized {
10372            self.write_space();
10373            self.write_keyword("MATERIALIZED");
10374        }
10375
10376        // Snowflake: SECURE VIEW
10377        if cv.secure {
10378            self.write_space();
10379            self.write_keyword("SECURE");
10380        }
10381
10382        self.write_space();
10383        self.write_keyword("VIEW");
10384
10385        if cv.if_not_exists {
10386            self.write_space();
10387            self.write_keyword("IF NOT EXISTS");
10388        }
10389
10390        self.write_space();
10391        self.generate_table(&cv.name)?;
10392
10393        // ClickHouse: ON CLUSTER clause
10394        if let Some(ref on_cluster) = cv.on_cluster {
10395            self.write_space();
10396            self.generate_on_cluster(on_cluster)?;
10397        }
10398
10399        // ClickHouse: TO destination_table
10400        if let Some(ref to_table) = cv.to_table {
10401            self.write_space();
10402            self.write_keyword("TO");
10403            self.write_space();
10404            self.generate_table(to_table)?;
10405        }
10406
10407        // For regular VIEW: columns come before COPY GRANTS
10408        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10409        if !cv.materialized {
10410            // Regular VIEW: columns first
10411            if !cv.columns.is_empty() {
10412                self.write(" (");
10413                for (i, col) in cv.columns.iter().enumerate() {
10414                    if i > 0 {
10415                        self.write(", ");
10416                    }
10417                    self.generate_identifier(&col.name)?;
10418                    // BigQuery: OPTIONS (key=value, ...) on view column
10419                    if !col.options.is_empty() {
10420                        self.write_space();
10421                        self.generate_options_clause(&col.options)?;
10422                    }
10423                    if let Some(ref comment) = col.comment {
10424                        self.write_space();
10425                        self.write_keyword("COMMENT");
10426                        self.write_space();
10427                        self.generate_string_literal(comment)?;
10428                    }
10429                }
10430                self.write(")");
10431            }
10432
10433            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10434            if !cv.security_sql_style {
10435                if let Some(ref security) = cv.security {
10436                    self.write_space();
10437                    self.write_keyword("SECURITY");
10438                    self.write_space();
10439                    match security {
10440                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10441                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10442                        FunctionSecurity::None => self.write_keyword("NONE"),
10443                    }
10444                }
10445            }
10446
10447            // Snowflake: COPY GRANTS
10448            if cv.copy_grants {
10449                self.write_space();
10450                self.write_keyword("COPY GRANTS");
10451            }
10452        } else {
10453            // MATERIALIZED VIEW: COPY GRANTS first
10454            if cv.copy_grants {
10455                self.write_space();
10456                self.write_keyword("COPY GRANTS");
10457            }
10458
10459            // Doris: If we have a schema (typed columns), generate that instead
10460            if let Some(ref schema) = cv.schema {
10461                self.write(" (");
10462                for (i, expr) in schema.expressions.iter().enumerate() {
10463                    if i > 0 {
10464                        self.write(", ");
10465                    }
10466                    self.generate_expression(expr)?;
10467                }
10468                self.write(")");
10469            } else if !cv.columns.is_empty() {
10470                // Then columns (simple column names without types)
10471                self.write(" (");
10472                for (i, col) in cv.columns.iter().enumerate() {
10473                    if i > 0 {
10474                        self.write(", ");
10475                    }
10476                    self.generate_identifier(&col.name)?;
10477                    // BigQuery: OPTIONS (key=value, ...) on view column
10478                    if !col.options.is_empty() {
10479                        self.write_space();
10480                        self.generate_options_clause(&col.options)?;
10481                    }
10482                    if let Some(ref comment) = col.comment {
10483                        self.write_space();
10484                        self.write_keyword("COMMENT");
10485                        self.write_space();
10486                        self.generate_string_literal(comment)?;
10487                    }
10488                }
10489                self.write(")");
10490            }
10491
10492            // Doris: KEY (columns) for materialized views
10493            if let Some(ref unique_key) = cv.unique_key {
10494                self.write_space();
10495                self.write_keyword("KEY");
10496                self.write(" (");
10497                for (i, expr) in unique_key.expressions.iter().enumerate() {
10498                    if i > 0 {
10499                        self.write(", ");
10500                    }
10501                    self.generate_expression(expr)?;
10502                }
10503                self.write(")");
10504            }
10505        }
10506
10507        // Snowflake: COMMENT = 'text'
10508        if let Some(ref comment) = cv.comment {
10509            self.write_space();
10510            self.write_keyword("COMMENT");
10511            self.write("=");
10512            self.generate_string_literal(comment)?;
10513        }
10514
10515        // Snowflake: TAG (name='value', ...)
10516        if !cv.tags.is_empty() {
10517            self.write_space();
10518            self.write_keyword("TAG");
10519            self.write(" (");
10520            for (i, (name, value)) in cv.tags.iter().enumerate() {
10521                if i > 0 {
10522                    self.write(", ");
10523                }
10524                self.write(name);
10525                self.write("='");
10526                self.write(value);
10527                self.write("'");
10528            }
10529            self.write(")");
10530        }
10531
10532        // BigQuery: OPTIONS (key=value, ...)
10533        if !cv.options.is_empty() {
10534            self.write_space();
10535            self.generate_options_clause(&cv.options)?;
10536        }
10537
10538        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10539        if let Some(ref build) = cv.build {
10540            self.write_space();
10541            self.write_keyword("BUILD");
10542            self.write_space();
10543            self.write_keyword(build);
10544        }
10545
10546        // Doris: REFRESH clause for materialized views
10547        if let Some(ref refresh) = cv.refresh {
10548            self.write_space();
10549            self.generate_refresh_trigger_property(refresh)?;
10550        }
10551
10552        // Redshift: AUTO REFRESH YES|NO for materialized views
10553        if let Some(auto_refresh) = cv.auto_refresh {
10554            self.write_space();
10555            self.write_keyword("AUTO REFRESH");
10556            self.write_space();
10557            if auto_refresh {
10558                self.write_keyword("YES");
10559            } else {
10560                self.write_keyword("NO");
10561            }
10562        }
10563
10564        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10565        for prop in &cv.table_properties {
10566            self.write_space();
10567            self.generate_expression(prop)?;
10568        }
10569
10570        // Only output AS clause if there's a real query (not just NULL placeholder)
10571        if !matches!(&cv.query, Expression::Null(_)) {
10572            self.write_space();
10573            self.write_keyword("AS");
10574            self.write_space();
10575
10576            // Teradata: LOCKING clause (between AS and query)
10577            if let Some(ref mode) = cv.locking_mode {
10578                self.write_keyword("LOCKING");
10579                self.write_space();
10580                self.write_keyword(mode);
10581                if let Some(ref access) = cv.locking_access {
10582                    self.write_space();
10583                    self.write_keyword("FOR");
10584                    self.write_space();
10585                    self.write_keyword(access);
10586                }
10587                self.write_space();
10588            }
10589
10590            if cv.query_parenthesized {
10591                self.write("(");
10592            }
10593            self.generate_expression(&cv.query)?;
10594            if cv.query_parenthesized {
10595                self.write(")");
10596            }
10597        }
10598
10599        // Redshift: WITH NO SCHEMA BINDING (after query)
10600        if cv.no_schema_binding {
10601            self.write_space();
10602            self.write_keyword("WITH NO SCHEMA BINDING");
10603        }
10604
10605        Ok(())
10606    }
10607
10608    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10609        self.write_keyword("DROP");
10610
10611        if dv.materialized {
10612            self.write_space();
10613            self.write_keyword("MATERIALIZED");
10614        }
10615
10616        self.write_space();
10617        self.write_keyword("VIEW");
10618
10619        if dv.if_exists {
10620            self.write_space();
10621            self.write_keyword("IF EXISTS");
10622        }
10623
10624        self.write_space();
10625        self.generate_table(&dv.name)?;
10626
10627        Ok(())
10628    }
10629
10630    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10631        match tr.target {
10632            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10633            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10634        }
10635        if tr.if_exists {
10636            self.write_space();
10637            self.write_keyword("IF EXISTS");
10638        }
10639        self.write_space();
10640        self.generate_table(&tr.table)?;
10641
10642        // ClickHouse: ON CLUSTER clause
10643        if let Some(ref on_cluster) = tr.on_cluster {
10644            self.write_space();
10645            self.generate_on_cluster(on_cluster)?;
10646        }
10647
10648        // Check if first table has a * (multi-table with star)
10649        if !tr.extra_tables.is_empty() {
10650            // Check if the first entry matches the main table (star case)
10651            let skip_first = if let Some(first) = tr.extra_tables.first() {
10652                first.table.name == tr.table.name && first.star
10653            } else {
10654                false
10655            };
10656
10657            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10658            let strip_star = matches!(
10659                self.config.dialect,
10660                Some(crate::dialects::DialectType::PostgreSQL)
10661                    | Some(crate::dialects::DialectType::Redshift)
10662            );
10663            if skip_first && !strip_star {
10664                self.write("*");
10665            }
10666
10667            // Generate additional tables
10668            for (i, entry) in tr.extra_tables.iter().enumerate() {
10669                if i == 0 && skip_first {
10670                    continue; // Already handled the star for first table
10671                }
10672                self.write(", ");
10673                self.generate_table(&entry.table)?;
10674                if entry.star && !strip_star {
10675                    self.write("*");
10676                }
10677            }
10678        }
10679
10680        // RESTART/CONTINUE IDENTITY
10681        if let Some(identity) = &tr.identity {
10682            self.write_space();
10683            match identity {
10684                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10685                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10686            }
10687        }
10688
10689        if tr.cascade {
10690            self.write_space();
10691            self.write_keyword("CASCADE");
10692        }
10693
10694        if tr.restrict {
10695            self.write_space();
10696            self.write_keyword("RESTRICT");
10697        }
10698
10699        // Output Hive PARTITION clause
10700        if let Some(ref partition) = tr.partition {
10701            self.write_space();
10702            self.generate_expression(partition)?;
10703        }
10704
10705        Ok(())
10706    }
10707
10708    fn generate_use(&mut self, u: &Use) -> Result<()> {
10709        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10710        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10711            self.write_keyword("DATABASE");
10712            self.write_space();
10713            self.generate_identifier(&u.this)?;
10714            return Ok(());
10715        }
10716
10717        self.write_keyword("USE");
10718
10719        if let Some(kind) = &u.kind {
10720            self.write_space();
10721            match kind {
10722                UseKind::Database => self.write_keyword("DATABASE"),
10723                UseKind::Schema => self.write_keyword("SCHEMA"),
10724                UseKind::Role => self.write_keyword("ROLE"),
10725                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10726                UseKind::Catalog => self.write_keyword("CATALOG"),
10727                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10728            }
10729        }
10730
10731        self.write_space();
10732        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10733        // without quoting, since these are keywords not identifiers
10734        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10735            self.write(&u.this.name);
10736        } else {
10737            self.generate_identifier(&u.this)?;
10738        }
10739        Ok(())
10740    }
10741
10742    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10743        self.write_keyword("CACHE");
10744        if c.lazy {
10745            self.write_space();
10746            self.write_keyword("LAZY");
10747        }
10748        self.write_space();
10749        self.write_keyword("TABLE");
10750        self.write_space();
10751        self.generate_identifier(&c.table)?;
10752
10753        // OPTIONS clause
10754        if !c.options.is_empty() {
10755            self.write_space();
10756            self.write_keyword("OPTIONS");
10757            self.write("(");
10758            for (i, (key, value)) in c.options.iter().enumerate() {
10759                if i > 0 {
10760                    self.write(", ");
10761                }
10762                self.generate_expression(key)?;
10763                self.write(" = ");
10764                self.generate_expression(value)?;
10765            }
10766            self.write(")");
10767        }
10768
10769        // AS query
10770        if let Some(query) = &c.query {
10771            self.write_space();
10772            self.write_keyword("AS");
10773            self.write_space();
10774            self.generate_expression(query)?;
10775        }
10776
10777        Ok(())
10778    }
10779
10780    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10781        self.write_keyword("UNCACHE TABLE");
10782        if u.if_exists {
10783            self.write_space();
10784            self.write_keyword("IF EXISTS");
10785        }
10786        self.write_space();
10787        self.generate_identifier(&u.table)?;
10788        Ok(())
10789    }
10790
10791    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10792        self.write_keyword("LOAD DATA");
10793        if l.local {
10794            self.write_space();
10795            self.write_keyword("LOCAL");
10796        }
10797        self.write_space();
10798        self.write_keyword("INPATH");
10799        self.write_space();
10800        self.write("'");
10801        self.write(&l.inpath);
10802        self.write("'");
10803
10804        if l.overwrite {
10805            self.write_space();
10806            self.write_keyword("OVERWRITE");
10807        }
10808
10809        self.write_space();
10810        self.write_keyword("INTO TABLE");
10811        self.write_space();
10812        self.generate_expression(&l.table)?;
10813
10814        // PARTITION clause
10815        if !l.partition.is_empty() {
10816            self.write_space();
10817            self.write_keyword("PARTITION");
10818            self.write("(");
10819            for (i, (col, val)) in l.partition.iter().enumerate() {
10820                if i > 0 {
10821                    self.write(", ");
10822                }
10823                self.generate_identifier(col)?;
10824                self.write(" = ");
10825                self.generate_expression(val)?;
10826            }
10827            self.write(")");
10828        }
10829
10830        // INPUTFORMAT clause
10831        if let Some(fmt) = &l.input_format {
10832            self.write_space();
10833            self.write_keyword("INPUTFORMAT");
10834            self.write_space();
10835            self.write("'");
10836            self.write(fmt);
10837            self.write("'");
10838        }
10839
10840        // SERDE clause
10841        if let Some(serde) = &l.serde {
10842            self.write_space();
10843            self.write_keyword("SERDE");
10844            self.write_space();
10845            self.write("'");
10846            self.write(serde);
10847            self.write("'");
10848        }
10849
10850        Ok(())
10851    }
10852
10853    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10854        self.write_keyword("PRAGMA");
10855        self.write_space();
10856
10857        // Schema prefix if present
10858        if let Some(schema) = &p.schema {
10859            self.generate_identifier(schema)?;
10860            self.write(".");
10861        }
10862
10863        // Pragma name
10864        self.generate_identifier(&p.name)?;
10865
10866        // Value assignment or function call
10867        if let Some(value) = &p.value {
10868            self.write(" = ");
10869            self.generate_expression(value)?;
10870        } else if !p.args.is_empty() {
10871            self.write("(");
10872            for (i, arg) in p.args.iter().enumerate() {
10873                if i > 0 {
10874                    self.write(", ");
10875                }
10876                self.generate_expression(arg)?;
10877            }
10878            self.write(")");
10879        }
10880
10881        Ok(())
10882    }
10883
10884    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10885        self.write_keyword("GRANT");
10886        self.write_space();
10887
10888        // Privileges (with optional column lists)
10889        for (i, privilege) in g.privileges.iter().enumerate() {
10890            if i > 0 {
10891                self.write(", ");
10892            }
10893            self.write_keyword(&privilege.name);
10894            // Output column list if present: SELECT(col1, col2)
10895            if !privilege.columns.is_empty() {
10896                self.write("(");
10897                for (j, col) in privilege.columns.iter().enumerate() {
10898                    if j > 0 {
10899                        self.write(", ");
10900                    }
10901                    self.write(col);
10902                }
10903                self.write(")");
10904            }
10905        }
10906
10907        self.write_space();
10908        self.write_keyword("ON");
10909        self.write_space();
10910
10911        // Object kind (TABLE, SCHEMA, etc.)
10912        if let Some(kind) = &g.kind {
10913            self.write_keyword(kind);
10914            self.write_space();
10915        }
10916
10917        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10918        {
10919            use crate::dialects::DialectType;
10920            let should_upper = matches!(
10921                self.config.dialect,
10922                Some(DialectType::PostgreSQL)
10923                    | Some(DialectType::CockroachDB)
10924                    | Some(DialectType::Materialize)
10925                    | Some(DialectType::RisingWave)
10926            ) && (g.kind.as_deref() == Some("FUNCTION")
10927                || g.kind.as_deref() == Some("PROCEDURE"));
10928            if should_upper {
10929                use crate::expressions::Identifier;
10930                let upper_id = Identifier {
10931                    name: g.securable.name.to_uppercase(),
10932                    quoted: g.securable.quoted,
10933                    ..g.securable.clone()
10934                };
10935                self.generate_identifier(&upper_id)?;
10936            } else {
10937                self.generate_identifier(&g.securable)?;
10938            }
10939        }
10940
10941        // Function parameter types (if present)
10942        if !g.function_params.is_empty() {
10943            self.write("(");
10944            for (i, param) in g.function_params.iter().enumerate() {
10945                if i > 0 {
10946                    self.write(", ");
10947                }
10948                self.write(param);
10949            }
10950            self.write(")");
10951        }
10952
10953        self.write_space();
10954        self.write_keyword("TO");
10955        self.write_space();
10956
10957        // Principals
10958        for (i, principal) in g.principals.iter().enumerate() {
10959            if i > 0 {
10960                self.write(", ");
10961            }
10962            if principal.is_role {
10963                self.write_keyword("ROLE");
10964                self.write_space();
10965            } else if principal.is_group {
10966                self.write_keyword("GROUP");
10967                self.write_space();
10968            }
10969            self.generate_identifier(&principal.name)?;
10970        }
10971
10972        // WITH GRANT OPTION
10973        if g.grant_option {
10974            self.write_space();
10975            self.write_keyword("WITH GRANT OPTION");
10976        }
10977
10978        // TSQL: AS principal
10979        if let Some(ref principal) = g.as_principal {
10980            self.write_space();
10981            self.write_keyword("AS");
10982            self.write_space();
10983            self.generate_identifier(principal)?;
10984        }
10985
10986        Ok(())
10987    }
10988
10989    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10990        self.write_keyword("REVOKE");
10991        self.write_space();
10992
10993        // GRANT OPTION FOR
10994        if r.grant_option {
10995            self.write_keyword("GRANT OPTION FOR");
10996            self.write_space();
10997        }
10998
10999        // Privileges (with optional column lists)
11000        for (i, privilege) in r.privileges.iter().enumerate() {
11001            if i > 0 {
11002                self.write(", ");
11003            }
11004            self.write_keyword(&privilege.name);
11005            // Output column list if present: SELECT(col1, col2)
11006            if !privilege.columns.is_empty() {
11007                self.write("(");
11008                for (j, col) in privilege.columns.iter().enumerate() {
11009                    if j > 0 {
11010                        self.write(", ");
11011                    }
11012                    self.write(col);
11013                }
11014                self.write(")");
11015            }
11016        }
11017
11018        self.write_space();
11019        self.write_keyword("ON");
11020        self.write_space();
11021
11022        // Object kind
11023        if let Some(kind) = &r.kind {
11024            self.write_keyword(kind);
11025            self.write_space();
11026        }
11027
11028        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11029        {
11030            use crate::dialects::DialectType;
11031            let should_upper = matches!(
11032                self.config.dialect,
11033                Some(DialectType::PostgreSQL)
11034                    | Some(DialectType::CockroachDB)
11035                    | Some(DialectType::Materialize)
11036                    | Some(DialectType::RisingWave)
11037            ) && (r.kind.as_deref() == Some("FUNCTION")
11038                || r.kind.as_deref() == Some("PROCEDURE"));
11039            if should_upper {
11040                use crate::expressions::Identifier;
11041                let upper_id = Identifier {
11042                    name: r.securable.name.to_uppercase(),
11043                    quoted: r.securable.quoted,
11044                    ..r.securable.clone()
11045                };
11046                self.generate_identifier(&upper_id)?;
11047            } else {
11048                self.generate_identifier(&r.securable)?;
11049            }
11050        }
11051
11052        // Function parameter types (if present)
11053        if !r.function_params.is_empty() {
11054            self.write("(");
11055            for (i, param) in r.function_params.iter().enumerate() {
11056                if i > 0 {
11057                    self.write(", ");
11058                }
11059                self.write(param);
11060            }
11061            self.write(")");
11062        }
11063
11064        self.write_space();
11065        self.write_keyword("FROM");
11066        self.write_space();
11067
11068        // Principals
11069        for (i, principal) in r.principals.iter().enumerate() {
11070            if i > 0 {
11071                self.write(", ");
11072            }
11073            if principal.is_role {
11074                self.write_keyword("ROLE");
11075                self.write_space();
11076            } else if principal.is_group {
11077                self.write_keyword("GROUP");
11078                self.write_space();
11079            }
11080            self.generate_identifier(&principal.name)?;
11081        }
11082
11083        // CASCADE or RESTRICT
11084        if r.cascade {
11085            self.write_space();
11086            self.write_keyword("CASCADE");
11087        } else if r.restrict {
11088            self.write_space();
11089            self.write_keyword("RESTRICT");
11090        }
11091
11092        Ok(())
11093    }
11094
11095    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11096        self.write_keyword("COMMENT");
11097
11098        // IF EXISTS
11099        if c.exists {
11100            self.write_space();
11101            self.write_keyword("IF EXISTS");
11102        }
11103
11104        self.write_space();
11105        self.write_keyword("ON");
11106
11107        // MATERIALIZED
11108        if c.materialized {
11109            self.write_space();
11110            self.write_keyword("MATERIALIZED");
11111        }
11112
11113        self.write_space();
11114        self.write_keyword(&c.kind);
11115        self.write_space();
11116
11117        // Object name
11118        self.generate_expression(&c.this)?;
11119
11120        self.write_space();
11121        self.write_keyword("IS");
11122        self.write_space();
11123
11124        // Comment expression
11125        self.generate_expression(&c.expression)?;
11126
11127        Ok(())
11128    }
11129
11130    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11131        self.write_keyword("SET");
11132
11133        for (i, item) in s.items.iter().enumerate() {
11134            if i > 0 {
11135                self.write(",");
11136            }
11137            self.write_space();
11138
11139            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11140            if let Some(ref kind) = item.kind {
11141                self.write_keyword(kind);
11142                self.write_space();
11143            }
11144
11145            // Check for special SET forms by name
11146            let name_str = match &item.name {
11147                Expression::Identifier(id) => Some(id.name.as_str()),
11148                _ => None,
11149            };
11150
11151            let is_transaction = name_str == Some("TRANSACTION");
11152            let is_character_set = name_str == Some("CHARACTER SET");
11153            let is_names = name_str == Some("NAMES");
11154            let is_collate = name_str == Some("COLLATE");
11155            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11156            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11157            let is_variable = has_variable_kind || name_has_variable_prefix;
11158            let is_value_only =
11159                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11160
11161            if is_transaction {
11162                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11163                self.write_keyword("TRANSACTION");
11164                if let Expression::Identifier(id) = &item.value {
11165                    if !id.name.is_empty() {
11166                        self.write_space();
11167                        self.write(&id.name);
11168                    }
11169                }
11170            } else if is_character_set {
11171                // Output: SET CHARACTER SET <charset>
11172                self.write_keyword("CHARACTER SET");
11173                self.write_space();
11174                self.generate_set_value(&item.value)?;
11175            } else if is_names {
11176                // Output: SET NAMES <charset>
11177                self.write_keyword("NAMES");
11178                self.write_space();
11179                self.generate_set_value(&item.value)?;
11180            } else if is_collate {
11181                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11182                self.write_keyword("COLLATE");
11183                self.write_space();
11184                self.generate_set_value(&item.value)?;
11185            } else if is_variable {
11186                // Output: SET [VARIABLE] <name> = <value>
11187                // If kind=VARIABLE, the keyword was already written above.
11188                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11189                if name_has_variable_prefix && !has_variable_kind {
11190                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11191                        self.write_keyword("VARIABLE");
11192                        self.write_space();
11193                    }
11194                }
11195                // Extract actual variable name (strip VARIABLE prefix if present)
11196                if let Some(ns) = name_str {
11197                    let var_name = if name_has_variable_prefix {
11198                        &ns["VARIABLE ".len()..]
11199                    } else {
11200                        ns
11201                    };
11202                    self.write(var_name);
11203                } else {
11204                    self.generate_expression(&item.name)?;
11205                }
11206                self.write(" = ");
11207                self.generate_set_value(&item.value)?;
11208            } else if is_value_only {
11209                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11210                self.generate_expression(&item.name)?;
11211            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11212                // SET key value without = (TSQL style)
11213                self.generate_expression(&item.name)?;
11214                self.write_space();
11215                self.generate_set_value(&item.value)?;
11216            } else {
11217                // Standard: variable = value
11218                // SET item names should not be quoted (they are config parameter names, not column refs)
11219                match &item.name {
11220                    Expression::Identifier(id) => {
11221                        self.write(&id.name);
11222                    }
11223                    _ => {
11224                        self.generate_expression(&item.name)?;
11225                    }
11226                }
11227                self.write(" = ");
11228                self.generate_set_value(&item.value)?;
11229            }
11230        }
11231
11232        Ok(())
11233    }
11234
11235    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11236    /// directly to avoid reserved keyword quoting.
11237    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11238        if let Expression::Identifier(id) = value {
11239            match id.name.as_str() {
11240                "DEFAULT" | "ON" | "OFF" => {
11241                    self.write_keyword(&id.name);
11242                    return Ok(());
11243                }
11244                _ => {}
11245            }
11246        }
11247        self.generate_expression(value)
11248    }
11249
11250    // ==================== Phase 4: Additional DDL Generation ====================
11251
11252    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11253        self.write_keyword("ALTER");
11254        // MySQL modifiers before VIEW
11255        if let Some(ref algorithm) = av.algorithm {
11256            self.write_space();
11257            self.write_keyword("ALGORITHM");
11258            self.write(" = ");
11259            self.write_keyword(algorithm);
11260        }
11261        if let Some(ref definer) = av.definer {
11262            self.write_space();
11263            self.write_keyword("DEFINER");
11264            self.write(" = ");
11265            self.write(definer);
11266        }
11267        if let Some(ref sql_security) = av.sql_security {
11268            self.write_space();
11269            self.write_keyword("SQL SECURITY");
11270            self.write(" = ");
11271            self.write_keyword(sql_security);
11272        }
11273        self.write_space();
11274        self.write_keyword("VIEW");
11275        self.write_space();
11276        self.generate_table(&av.name)?;
11277
11278        // Hive: Column aliases with optional COMMENT
11279        if !av.columns.is_empty() {
11280            self.write(" (");
11281            for (i, col) in av.columns.iter().enumerate() {
11282                if i > 0 {
11283                    self.write(", ");
11284                }
11285                self.generate_identifier(&col.name)?;
11286                if let Some(ref comment) = col.comment {
11287                    self.write_space();
11288                    self.write_keyword("COMMENT");
11289                    self.write(" ");
11290                    self.generate_string_literal(comment)?;
11291                }
11292            }
11293            self.write(")");
11294        }
11295
11296        // TSQL: WITH option before actions
11297        if let Some(ref opt) = av.with_option {
11298            self.write_space();
11299            self.write_keyword("WITH");
11300            self.write_space();
11301            self.write_keyword(opt);
11302        }
11303
11304        for action in &av.actions {
11305            self.write_space();
11306            match action {
11307                AlterViewAction::Rename(new_name) => {
11308                    self.write_keyword("RENAME TO");
11309                    self.write_space();
11310                    self.generate_table(new_name)?;
11311                }
11312                AlterViewAction::OwnerTo(owner) => {
11313                    self.write_keyword("OWNER TO");
11314                    self.write_space();
11315                    self.generate_identifier(owner)?;
11316                }
11317                AlterViewAction::SetSchema(schema) => {
11318                    self.write_keyword("SET SCHEMA");
11319                    self.write_space();
11320                    self.generate_identifier(schema)?;
11321                }
11322                AlterViewAction::SetAuthorization(auth) => {
11323                    self.write_keyword("SET AUTHORIZATION");
11324                    self.write_space();
11325                    self.write(auth);
11326                }
11327                AlterViewAction::AlterColumn { name, action } => {
11328                    self.write_keyword("ALTER COLUMN");
11329                    self.write_space();
11330                    self.generate_identifier(name)?;
11331                    self.write_space();
11332                    self.generate_alter_column_action(action)?;
11333                }
11334                AlterViewAction::AsSelect(query) => {
11335                    self.write_keyword("AS");
11336                    self.write_space();
11337                    self.generate_expression(query)?;
11338                }
11339                AlterViewAction::SetTblproperties(props) => {
11340                    self.write_keyword("SET TBLPROPERTIES");
11341                    self.write(" (");
11342                    for (i, (key, value)) in props.iter().enumerate() {
11343                        if i > 0 {
11344                            self.write(", ");
11345                        }
11346                        self.generate_string_literal(key)?;
11347                        self.write("=");
11348                        self.generate_string_literal(value)?;
11349                    }
11350                    self.write(")");
11351                }
11352                AlterViewAction::UnsetTblproperties(keys) => {
11353                    self.write_keyword("UNSET TBLPROPERTIES");
11354                    self.write(" (");
11355                    for (i, key) in keys.iter().enumerate() {
11356                        if i > 0 {
11357                            self.write(", ");
11358                        }
11359                        self.generate_string_literal(key)?;
11360                    }
11361                    self.write(")");
11362                }
11363            }
11364        }
11365
11366        Ok(())
11367    }
11368
11369    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11370        self.write_keyword("ALTER INDEX");
11371        self.write_space();
11372        self.generate_identifier(&ai.name)?;
11373
11374        if let Some(table) = &ai.table {
11375            self.write_space();
11376            self.write_keyword("ON");
11377            self.write_space();
11378            self.generate_table(table)?;
11379        }
11380
11381        for action in &ai.actions {
11382            self.write_space();
11383            match action {
11384                AlterIndexAction::Rename(new_name) => {
11385                    self.write_keyword("RENAME TO");
11386                    self.write_space();
11387                    self.generate_identifier(new_name)?;
11388                }
11389                AlterIndexAction::SetTablespace(tablespace) => {
11390                    self.write_keyword("SET TABLESPACE");
11391                    self.write_space();
11392                    self.generate_identifier(tablespace)?;
11393                }
11394                AlterIndexAction::Visible(visible) => {
11395                    if *visible {
11396                        self.write_keyword("VISIBLE");
11397                    } else {
11398                        self.write_keyword("INVISIBLE");
11399                    }
11400                }
11401            }
11402        }
11403
11404        Ok(())
11405    }
11406
11407    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11408        // Output leading comments
11409        for comment in &cs.leading_comments {
11410            self.write_formatted_comment(comment);
11411            self.write_space();
11412        }
11413
11414        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11415        let saved_athena_hive_context = self.athena_hive_context;
11416        if matches!(
11417            self.config.dialect,
11418            Some(crate::dialects::DialectType::Athena)
11419        ) {
11420            self.athena_hive_context = true;
11421        }
11422
11423        self.write_keyword("CREATE SCHEMA");
11424
11425        if cs.if_not_exists {
11426            self.write_space();
11427            self.write_keyword("IF NOT EXISTS");
11428        }
11429
11430        self.write_space();
11431        self.generate_identifier(&cs.name)?;
11432
11433        if let Some(ref clone_src) = cs.clone_from {
11434            self.write_keyword(" CLONE ");
11435            self.generate_identifier(clone_src)?;
11436        }
11437
11438        if let Some(ref at_clause) = cs.at_clause {
11439            self.write_space();
11440            self.generate_expression(at_clause)?;
11441        }
11442
11443        if let Some(auth) = &cs.authorization {
11444            self.write_space();
11445            self.write_keyword("AUTHORIZATION");
11446            self.write_space();
11447            self.generate_identifier(auth)?;
11448        }
11449
11450        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11451        // Separate WITH properties from other properties
11452        let with_properties: Vec<_> = cs
11453            .properties
11454            .iter()
11455            .filter(|p| matches!(p, Expression::Property(_)))
11456            .collect();
11457        let other_properties: Vec<_> = cs
11458            .properties
11459            .iter()
11460            .filter(|p| !matches!(p, Expression::Property(_)))
11461            .collect();
11462
11463        // Generate WITH (props) if we have Property expressions
11464        if !with_properties.is_empty() {
11465            self.write_space();
11466            self.write_keyword("WITH");
11467            self.write(" (");
11468            for (i, prop) in with_properties.iter().enumerate() {
11469                if i > 0 {
11470                    self.write(", ");
11471                }
11472                self.generate_expression(prop)?;
11473            }
11474            self.write(")");
11475        }
11476
11477        // Generate other properties (like DEFAULT COLLATE)
11478        for prop in other_properties {
11479            self.write_space();
11480            self.generate_expression(prop)?;
11481        }
11482
11483        // Restore Athena Hive context
11484        self.athena_hive_context = saved_athena_hive_context;
11485
11486        Ok(())
11487    }
11488
11489    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11490        self.write_keyword("DROP SCHEMA");
11491
11492        if ds.if_exists {
11493            self.write_space();
11494            self.write_keyword("IF EXISTS");
11495        }
11496
11497        self.write_space();
11498        self.generate_identifier(&ds.name)?;
11499
11500        if ds.cascade {
11501            self.write_space();
11502            self.write_keyword("CASCADE");
11503        }
11504
11505        Ok(())
11506    }
11507
11508    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11509        self.write_keyword("DROP NAMESPACE");
11510
11511        if dn.if_exists {
11512            self.write_space();
11513            self.write_keyword("IF EXISTS");
11514        }
11515
11516        self.write_space();
11517        self.generate_identifier(&dn.name)?;
11518
11519        if dn.cascade {
11520            self.write_space();
11521            self.write_keyword("CASCADE");
11522        }
11523
11524        Ok(())
11525    }
11526
11527    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11528        self.write_keyword("CREATE DATABASE");
11529
11530        if cd.if_not_exists {
11531            self.write_space();
11532            self.write_keyword("IF NOT EXISTS");
11533        }
11534
11535        self.write_space();
11536        self.generate_identifier(&cd.name)?;
11537
11538        if let Some(ref clone_src) = cd.clone_from {
11539            self.write_keyword(" CLONE ");
11540            self.generate_identifier(clone_src)?;
11541        }
11542
11543        // AT/BEFORE clause for time travel (Snowflake)
11544        if let Some(ref at_clause) = cd.at_clause {
11545            self.write_space();
11546            self.generate_expression(at_clause)?;
11547        }
11548
11549        for option in &cd.options {
11550            self.write_space();
11551            match option {
11552                DatabaseOption::CharacterSet(charset) => {
11553                    self.write_keyword("CHARACTER SET");
11554                    self.write(" = ");
11555                    self.write(&format!("'{}'", charset));
11556                }
11557                DatabaseOption::Collate(collate) => {
11558                    self.write_keyword("COLLATE");
11559                    self.write(" = ");
11560                    self.write(&format!("'{}'", collate));
11561                }
11562                DatabaseOption::Owner(owner) => {
11563                    self.write_keyword("OWNER");
11564                    self.write(" = ");
11565                    self.generate_identifier(owner)?;
11566                }
11567                DatabaseOption::Template(template) => {
11568                    self.write_keyword("TEMPLATE");
11569                    self.write(" = ");
11570                    self.generate_identifier(template)?;
11571                }
11572                DatabaseOption::Encoding(encoding) => {
11573                    self.write_keyword("ENCODING");
11574                    self.write(" = ");
11575                    self.write(&format!("'{}'", encoding));
11576                }
11577                DatabaseOption::Location(location) => {
11578                    self.write_keyword("LOCATION");
11579                    self.write(" = ");
11580                    self.write(&format!("'{}'", location));
11581                }
11582            }
11583        }
11584
11585        Ok(())
11586    }
11587
11588    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11589        self.write_keyword("DROP DATABASE");
11590
11591        if dd.if_exists {
11592            self.write_space();
11593            self.write_keyword("IF EXISTS");
11594        }
11595
11596        self.write_space();
11597        self.generate_identifier(&dd.name)?;
11598
11599        Ok(())
11600    }
11601
11602    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11603        self.write_keyword("CREATE");
11604
11605        if cf.or_replace {
11606            self.write_space();
11607            self.write_keyword("OR REPLACE");
11608        }
11609
11610        if cf.temporary {
11611            self.write_space();
11612            self.write_keyword("TEMPORARY");
11613        }
11614
11615        self.write_space();
11616        if cf.is_table_function {
11617            self.write_keyword("TABLE FUNCTION");
11618        } else {
11619            self.write_keyword("FUNCTION");
11620        }
11621
11622        if cf.if_not_exists {
11623            self.write_space();
11624            self.write_keyword("IF NOT EXISTS");
11625        }
11626
11627        self.write_space();
11628        self.generate_table(&cf.name)?;
11629        if cf.has_parens {
11630            let func_multiline = self.config.pretty
11631                && matches!(
11632                    self.config.dialect,
11633                    Some(crate::dialects::DialectType::TSQL)
11634                        | Some(crate::dialects::DialectType::Fabric)
11635                )
11636                && !cf.parameters.is_empty();
11637            if func_multiline {
11638                self.write("(\n");
11639                self.indent_level += 2;
11640                self.write_indent();
11641                self.generate_function_parameters(&cf.parameters)?;
11642                self.write("\n");
11643                self.indent_level -= 2;
11644                self.write(")");
11645            } else {
11646                self.write("(");
11647                self.generate_function_parameters(&cf.parameters)?;
11648                self.write(")");
11649            }
11650        }
11651
11652        // Output RETURNS clause (always comes first after parameters)
11653        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11654        let use_multiline = self.config.pretty
11655            && matches!(
11656                self.config.dialect,
11657                Some(crate::dialects::DialectType::BigQuery)
11658                    | Some(crate::dialects::DialectType::TSQL)
11659                    | Some(crate::dialects::DialectType::Fabric)
11660            );
11661
11662        if cf.language_first {
11663            // LANGUAGE first, then SQL data access, then RETURNS
11664            if let Some(lang) = &cf.language {
11665                if use_multiline {
11666                    self.write_newline();
11667                } else {
11668                    self.write_space();
11669                }
11670                self.write_keyword("LANGUAGE");
11671                self.write_space();
11672                self.write(lang);
11673            }
11674
11675            // SQL data access comes after LANGUAGE in this case
11676            if let Some(sql_data) = &cf.sql_data_access {
11677                self.write_space();
11678                match sql_data {
11679                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11680                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11681                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11682                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11683                }
11684            }
11685
11686            if let Some(ref rtb) = cf.returns_table_body {
11687                if use_multiline {
11688                    self.write_newline();
11689                } else {
11690                    self.write_space();
11691                }
11692                self.write_keyword("RETURNS");
11693                self.write_space();
11694                self.write(rtb);
11695            } else if let Some(return_type) = &cf.return_type {
11696                if use_multiline {
11697                    self.write_newline();
11698                } else {
11699                    self.write_space();
11700                }
11701                self.write_keyword("RETURNS");
11702                self.write_space();
11703                self.generate_data_type(return_type)?;
11704            }
11705        } else {
11706            // RETURNS first (default)
11707            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11708            let is_duckdb = matches!(
11709                self.config.dialect,
11710                Some(crate::dialects::DialectType::DuckDB)
11711            );
11712            if let Some(ref rtb) = cf.returns_table_body {
11713                if !(is_duckdb && rtb.is_empty()) {
11714                    if use_multiline {
11715                        self.write_newline();
11716                    } else {
11717                        self.write_space();
11718                    }
11719                    self.write_keyword("RETURNS");
11720                    self.write_space();
11721                    self.write(rtb);
11722                }
11723            } else if let Some(return_type) = &cf.return_type {
11724                if use_multiline {
11725                    self.write_newline();
11726                } else {
11727                    self.write_space();
11728                }
11729                self.write_keyword("RETURNS");
11730                self.write_space();
11731                self.generate_data_type(return_type)?;
11732            }
11733        }
11734
11735        // If we have property_order, use it to output properties in original order
11736        if !cf.property_order.is_empty() {
11737            // For BigQuery, OPTIONS must come before AS - reorder if needed
11738            let is_bigquery = matches!(
11739                self.config.dialect,
11740                Some(crate::dialects::DialectType::BigQuery)
11741            );
11742            let property_order = if is_bigquery {
11743                // Move Options before As if both are present
11744                let mut reordered = Vec::new();
11745                let mut has_as = false;
11746                let mut has_options = false;
11747                for prop in &cf.property_order {
11748                    match prop {
11749                        FunctionPropertyKind::As => has_as = true,
11750                        FunctionPropertyKind::Options => has_options = true,
11751                        _ => {}
11752                    }
11753                }
11754                if has_as && has_options {
11755                    // Output all props except As and Options, then Options, then As
11756                    for prop in &cf.property_order {
11757                        if *prop != FunctionPropertyKind::As
11758                            && *prop != FunctionPropertyKind::Options
11759                        {
11760                            reordered.push(*prop);
11761                        }
11762                    }
11763                    reordered.push(FunctionPropertyKind::Options);
11764                    reordered.push(FunctionPropertyKind::As);
11765                    reordered
11766                } else {
11767                    cf.property_order.clone()
11768                }
11769            } else {
11770                cf.property_order.clone()
11771            };
11772
11773            for prop in &property_order {
11774                match prop {
11775                    FunctionPropertyKind::Set => {
11776                        self.generate_function_set_options(cf)?;
11777                    }
11778                    FunctionPropertyKind::As => {
11779                        self.generate_function_body(cf)?;
11780                    }
11781                    FunctionPropertyKind::Language => {
11782                        if !cf.language_first {
11783                            // Only output here if not already output above
11784                            if let Some(lang) = &cf.language {
11785                                // Only BigQuery uses multiline formatting
11786                                let use_multiline = self.config.pretty
11787                                    && matches!(
11788                                        self.config.dialect,
11789                                        Some(crate::dialects::DialectType::BigQuery)
11790                                    );
11791                                if use_multiline {
11792                                    self.write_newline();
11793                                } else {
11794                                    self.write_space();
11795                                }
11796                                self.write_keyword("LANGUAGE");
11797                                self.write_space();
11798                                self.write(lang);
11799                            }
11800                        }
11801                    }
11802                    FunctionPropertyKind::Determinism => {
11803                        self.generate_function_determinism(cf)?;
11804                    }
11805                    FunctionPropertyKind::NullInput => {
11806                        self.generate_function_null_input(cf)?;
11807                    }
11808                    FunctionPropertyKind::Security => {
11809                        self.generate_function_security(cf)?;
11810                    }
11811                    FunctionPropertyKind::SqlDataAccess => {
11812                        if !cf.language_first {
11813                            // Only output here if not already output above
11814                            self.generate_function_sql_data_access(cf)?;
11815                        }
11816                    }
11817                    FunctionPropertyKind::Options => {
11818                        if !cf.options.is_empty() {
11819                            self.write_space();
11820                            self.generate_options_clause(&cf.options)?;
11821                        }
11822                    }
11823                    FunctionPropertyKind::Environment => {
11824                        if !cf.environment.is_empty() {
11825                            self.write_space();
11826                            self.generate_environment_clause(&cf.environment)?;
11827                        }
11828                    }
11829                }
11830            }
11831
11832            // Output OPTIONS if not tracked in property_order (legacy)
11833            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11834            {
11835                self.write_space();
11836                self.generate_options_clause(&cf.options)?;
11837            }
11838
11839            // Output ENVIRONMENT if not tracked in property_order (legacy)
11840            if !cf.environment.is_empty()
11841                && !cf
11842                    .property_order
11843                    .contains(&FunctionPropertyKind::Environment)
11844            {
11845                self.write_space();
11846                self.generate_environment_clause(&cf.environment)?;
11847            }
11848        } else {
11849            // Legacy behavior when property_order is empty
11850            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11851            if matches!(
11852                self.config.dialect,
11853                Some(crate::dialects::DialectType::BigQuery)
11854            ) {
11855                self.generate_function_determinism(cf)?;
11856            }
11857
11858            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11859            let use_multiline = self.config.pretty
11860                && matches!(
11861                    self.config.dialect,
11862                    Some(crate::dialects::DialectType::BigQuery)
11863                );
11864
11865            if !cf.language_first {
11866                if let Some(lang) = &cf.language {
11867                    if use_multiline {
11868                        self.write_newline();
11869                    } else {
11870                        self.write_space();
11871                    }
11872                    self.write_keyword("LANGUAGE");
11873                    self.write_space();
11874                    self.write(lang);
11875                }
11876
11877                // SQL data access characteristic comes after LANGUAGE
11878                self.generate_function_sql_data_access(cf)?;
11879            }
11880
11881            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11882            if !matches!(
11883                self.config.dialect,
11884                Some(crate::dialects::DialectType::BigQuery)
11885            ) {
11886                self.generate_function_determinism(cf)?;
11887            }
11888
11889            self.generate_function_null_input(cf)?;
11890            self.generate_function_security(cf)?;
11891            self.generate_function_set_options(cf)?;
11892
11893            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11894            if !cf.options.is_empty() {
11895                self.write_space();
11896                self.generate_options_clause(&cf.options)?;
11897            }
11898
11899            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11900            if !cf.environment.is_empty() {
11901                self.write_space();
11902                self.generate_environment_clause(&cf.environment)?;
11903            }
11904
11905            self.generate_function_body(cf)?;
11906        }
11907
11908        Ok(())
11909    }
11910
11911    /// Generate SET options for CREATE FUNCTION
11912    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11913        for opt in &cf.set_options {
11914            self.write_space();
11915            self.write_keyword("SET");
11916            self.write_space();
11917            self.write(&opt.name);
11918            match &opt.value {
11919                FunctionSetValue::Value { value, use_to } => {
11920                    if *use_to {
11921                        self.write(" TO ");
11922                    } else {
11923                        self.write(" = ");
11924                    }
11925                    self.write(value);
11926                }
11927                FunctionSetValue::FromCurrent => {
11928                    self.write_space();
11929                    self.write_keyword("FROM CURRENT");
11930                }
11931            }
11932        }
11933        Ok(())
11934    }
11935
11936    /// Generate function body (AS clause)
11937    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11938        if let Some(body) = &cf.body {
11939            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11940            self.write_space();
11941            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11942            let use_multiline = self.config.pretty
11943                && matches!(
11944                    self.config.dialect,
11945                    Some(crate::dialects::DialectType::BigQuery)
11946                );
11947            match body {
11948                FunctionBody::Block(block) => {
11949                    self.write_keyword("AS");
11950                    if matches!(
11951                        self.config.dialect,
11952                        Some(crate::dialects::DialectType::TSQL)
11953                    ) {
11954                        self.write(" BEGIN ");
11955                        self.write(block);
11956                        self.write(" END");
11957                    } else if matches!(
11958                        self.config.dialect,
11959                        Some(crate::dialects::DialectType::PostgreSQL)
11960                    ) {
11961                        self.write(" $$");
11962                        self.write(block);
11963                        self.write("$$");
11964                    } else {
11965                        // Escape content for single-quoted output
11966                        let escaped = self.escape_block_for_single_quote(block);
11967                        // In BigQuery pretty mode, body content goes on new line
11968                        if use_multiline {
11969                            self.write_newline();
11970                        } else {
11971                            self.write(" ");
11972                        }
11973                        self.write("'");
11974                        self.write(&escaped);
11975                        self.write("'");
11976                    }
11977                }
11978                FunctionBody::StringLiteral(s) => {
11979                    self.write_keyword("AS");
11980                    // In BigQuery pretty mode, body content goes on new line
11981                    if use_multiline {
11982                        self.write_newline();
11983                    } else {
11984                        self.write(" ");
11985                    }
11986                    self.write("'");
11987                    self.write(s);
11988                    self.write("'");
11989                }
11990                FunctionBody::Expression(expr) => {
11991                    self.write_keyword("AS");
11992                    self.write_space();
11993                    self.generate_expression(expr)?;
11994                }
11995                FunctionBody::External(name) => {
11996                    self.write_keyword("EXTERNAL NAME");
11997                    self.write(" '");
11998                    self.write(name);
11999                    self.write("'");
12000                }
12001                FunctionBody::Return(expr) => {
12002                    if matches!(
12003                        self.config.dialect,
12004                        Some(crate::dialects::DialectType::DuckDB)
12005                    ) {
12006                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12007                        self.write_keyword("AS");
12008                        self.write_space();
12009                        // Empty returns_table_body signals TABLE return
12010                        if cf.returns_table_body.is_some() {
12011                            self.write_keyword("TABLE");
12012                            self.write_space();
12013                        }
12014                        self.generate_expression(expr)?;
12015                    } else {
12016                        if self.config.create_function_return_as {
12017                            self.write_keyword("AS");
12018                            // TSQL pretty: newline between AS and RETURN
12019                            if self.config.pretty
12020                                && matches!(
12021                                    self.config.dialect,
12022                                    Some(crate::dialects::DialectType::TSQL)
12023                                        | Some(crate::dialects::DialectType::Fabric)
12024                                )
12025                            {
12026                                self.write_newline();
12027                            } else {
12028                                self.write_space();
12029                            }
12030                        }
12031                        self.write_keyword("RETURN");
12032                        self.write_space();
12033                        self.generate_expression(expr)?;
12034                    }
12035                }
12036                FunctionBody::Statements(stmts) => {
12037                    self.write_keyword("AS");
12038                    self.write(" BEGIN ");
12039                    for (i, stmt) in stmts.iter().enumerate() {
12040                        if i > 0 {
12041                            self.write(" ");
12042                        }
12043                        self.generate_expression(stmt)?;
12044                    }
12045                    self.write(" END");
12046                }
12047                FunctionBody::DollarQuoted { content, tag } => {
12048                    self.write_keyword("AS");
12049                    self.write(" ");
12050                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12051                    let supports_dollar_quoting = matches!(
12052                        self.config.dialect,
12053                        Some(crate::dialects::DialectType::PostgreSQL)
12054                            | Some(crate::dialects::DialectType::Databricks)
12055                            | Some(crate::dialects::DialectType::Redshift)
12056                            | Some(crate::dialects::DialectType::DuckDB)
12057                    );
12058                    if supports_dollar_quoting {
12059                        // Output in dollar-quoted format
12060                        self.write("$");
12061                        if let Some(t) = tag {
12062                            self.write(t);
12063                        }
12064                        self.write("$");
12065                        self.write(content);
12066                        self.write("$");
12067                        if let Some(t) = tag {
12068                            self.write(t);
12069                        }
12070                        self.write("$");
12071                    } else {
12072                        // Convert to single-quoted string for other dialects
12073                        let escaped = self.escape_block_for_single_quote(content);
12074                        self.write("'");
12075                        self.write(&escaped);
12076                        self.write("'");
12077                    }
12078                }
12079            }
12080        }
12081        Ok(())
12082    }
12083
12084    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12085    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12086        if let Some(det) = cf.deterministic {
12087            self.write_space();
12088            if matches!(
12089                self.config.dialect,
12090                Some(crate::dialects::DialectType::BigQuery)
12091            ) {
12092                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12093                if det {
12094                    self.write_keyword("DETERMINISTIC");
12095                } else {
12096                    self.write_keyword("NOT DETERMINISTIC");
12097                }
12098            } else {
12099                // PostgreSQL and others use IMMUTABLE/VOLATILE
12100                if det {
12101                    self.write_keyword("IMMUTABLE");
12102                } else {
12103                    self.write_keyword("VOLATILE");
12104                }
12105            }
12106        }
12107        Ok(())
12108    }
12109
12110    /// Generate null input handling clause
12111    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12112        if let Some(returns_null) = cf.returns_null_on_null_input {
12113            self.write_space();
12114            if returns_null {
12115                if cf.strict {
12116                    self.write_keyword("STRICT");
12117                } else {
12118                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12119                }
12120            } else {
12121                self.write_keyword("CALLED ON NULL INPUT");
12122            }
12123        }
12124        Ok(())
12125    }
12126
12127    /// Generate security clause
12128    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12129        if let Some(security) = &cf.security {
12130            self.write_space();
12131            self.write_keyword("SECURITY");
12132            self.write_space();
12133            match security {
12134                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12135                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12136                FunctionSecurity::None => self.write_keyword("NONE"),
12137            }
12138        }
12139        Ok(())
12140    }
12141
12142    /// Generate SQL data access clause
12143    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12144        if let Some(sql_data) = &cf.sql_data_access {
12145            self.write_space();
12146            match sql_data {
12147                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12148                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12149                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12150                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12151            }
12152        }
12153        Ok(())
12154    }
12155
12156    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12157        for (i, param) in params.iter().enumerate() {
12158            if i > 0 {
12159                self.write(", ");
12160            }
12161
12162            if let Some(mode) = &param.mode {
12163                if let Some(text) = &param.mode_text {
12164                    self.write(text);
12165                } else {
12166                    match mode {
12167                        ParameterMode::In => self.write_keyword("IN"),
12168                        ParameterMode::Out => self.write_keyword("OUT"),
12169                        ParameterMode::InOut => self.write_keyword("INOUT"),
12170                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12171                    }
12172                }
12173                self.write_space();
12174            }
12175
12176            if let Some(name) = &param.name {
12177                self.generate_identifier(name)?;
12178                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12179                let skip_type =
12180                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12181                if !skip_type {
12182                    self.write_space();
12183                    self.generate_data_type(&param.data_type)?;
12184                }
12185            } else {
12186                self.generate_data_type(&param.data_type)?;
12187            }
12188
12189            if let Some(default) = &param.default {
12190                if self.config.parameter_default_equals {
12191                    self.write(" = ");
12192                } else {
12193                    self.write(" DEFAULT ");
12194                }
12195                self.generate_expression(default)?;
12196            }
12197        }
12198
12199        Ok(())
12200    }
12201
12202    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12203        self.write_keyword("DROP FUNCTION");
12204
12205        if df.if_exists {
12206            self.write_space();
12207            self.write_keyword("IF EXISTS");
12208        }
12209
12210        self.write_space();
12211        self.generate_table(&df.name)?;
12212
12213        if let Some(params) = &df.parameters {
12214            self.write(" (");
12215            for (i, dt) in params.iter().enumerate() {
12216                if i > 0 {
12217                    self.write(", ");
12218                }
12219                self.generate_data_type(dt)?;
12220            }
12221            self.write(")");
12222        }
12223
12224        if df.cascade {
12225            self.write_space();
12226            self.write_keyword("CASCADE");
12227        }
12228
12229        Ok(())
12230    }
12231
12232    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12233        self.write_keyword("CREATE");
12234
12235        if cp.or_replace {
12236            self.write_space();
12237            self.write_keyword("OR REPLACE");
12238        }
12239
12240        self.write_space();
12241        if cp.use_proc_keyword {
12242            self.write_keyword("PROC");
12243        } else {
12244            self.write_keyword("PROCEDURE");
12245        }
12246
12247        if cp.if_not_exists {
12248            self.write_space();
12249            self.write_keyword("IF NOT EXISTS");
12250        }
12251
12252        self.write_space();
12253        self.generate_table(&cp.name)?;
12254        if cp.has_parens {
12255            self.write("(");
12256            self.generate_function_parameters(&cp.parameters)?;
12257            self.write(")");
12258        } else if !cp.parameters.is_empty() {
12259            // TSQL: unparenthesized parameters
12260            self.write_space();
12261            self.generate_function_parameters(&cp.parameters)?;
12262        }
12263
12264        // RETURNS clause (Snowflake)
12265        if let Some(return_type) = &cp.return_type {
12266            self.write_space();
12267            self.write_keyword("RETURNS");
12268            self.write_space();
12269            self.generate_data_type(return_type)?;
12270        }
12271
12272        // EXECUTE AS clause (Snowflake)
12273        if let Some(execute_as) = &cp.execute_as {
12274            self.write_space();
12275            self.write_keyword("EXECUTE AS");
12276            self.write_space();
12277            self.write_keyword(execute_as);
12278        }
12279
12280        if let Some(lang) = &cp.language {
12281            self.write_space();
12282            self.write_keyword("LANGUAGE");
12283            self.write_space();
12284            self.write(lang);
12285        }
12286
12287        if let Some(security) = &cp.security {
12288            self.write_space();
12289            self.write_keyword("SECURITY");
12290            self.write_space();
12291            match security {
12292                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12293                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12294                FunctionSecurity::None => self.write_keyword("NONE"),
12295            }
12296        }
12297
12298        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12299        if !cp.with_options.is_empty() {
12300            self.write_space();
12301            self.write_keyword("WITH");
12302            self.write_space();
12303            for (i, opt) in cp.with_options.iter().enumerate() {
12304                if i > 0 {
12305                    self.write(", ");
12306                }
12307                self.write(opt);
12308            }
12309        }
12310
12311        if let Some(body) = &cp.body {
12312            self.write_space();
12313            match body {
12314                FunctionBody::Block(block) => {
12315                    self.write_keyword("AS");
12316                    if matches!(
12317                        self.config.dialect,
12318                        Some(crate::dialects::DialectType::TSQL)
12319                    ) {
12320                        self.write(" BEGIN ");
12321                        self.write(block);
12322                        self.write(" END");
12323                    } else if matches!(
12324                        self.config.dialect,
12325                        Some(crate::dialects::DialectType::PostgreSQL)
12326                    ) {
12327                        self.write(" $$");
12328                        self.write(block);
12329                        self.write("$$");
12330                    } else {
12331                        // Escape content for single-quoted output
12332                        let escaped = self.escape_block_for_single_quote(block);
12333                        self.write(" '");
12334                        self.write(&escaped);
12335                        self.write("'");
12336                    }
12337                }
12338                FunctionBody::StringLiteral(s) => {
12339                    self.write_keyword("AS");
12340                    self.write(" '");
12341                    self.write(s);
12342                    self.write("'");
12343                }
12344                FunctionBody::Expression(expr) => {
12345                    self.write_keyword("AS");
12346                    self.write_space();
12347                    self.generate_expression(expr)?;
12348                }
12349                FunctionBody::External(name) => {
12350                    self.write_keyword("EXTERNAL NAME");
12351                    self.write(" '");
12352                    self.write(name);
12353                    self.write("'");
12354                }
12355                FunctionBody::Return(expr) => {
12356                    self.write_keyword("RETURN");
12357                    self.write_space();
12358                    self.generate_expression(expr)?;
12359                }
12360                FunctionBody::Statements(stmts) => {
12361                    self.write_keyword("AS");
12362                    self.write(" BEGIN ");
12363                    for (i, stmt) in stmts.iter().enumerate() {
12364                        if i > 0 {
12365                            self.write(" ");
12366                        }
12367                        self.generate_expression(stmt)?;
12368                    }
12369                    self.write(" END");
12370                }
12371                FunctionBody::DollarQuoted { content, tag } => {
12372                    self.write_keyword("AS");
12373                    self.write(" ");
12374                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12375                    let supports_dollar_quoting = matches!(
12376                        self.config.dialect,
12377                        Some(crate::dialects::DialectType::PostgreSQL)
12378                            | Some(crate::dialects::DialectType::Databricks)
12379                            | Some(crate::dialects::DialectType::Redshift)
12380                            | Some(crate::dialects::DialectType::DuckDB)
12381                    );
12382                    if supports_dollar_quoting {
12383                        // Output in dollar-quoted format
12384                        self.write("$");
12385                        if let Some(t) = tag {
12386                            self.write(t);
12387                        }
12388                        self.write("$");
12389                        self.write(content);
12390                        self.write("$");
12391                        if let Some(t) = tag {
12392                            self.write(t);
12393                        }
12394                        self.write("$");
12395                    } else {
12396                        // Convert to single-quoted string for other dialects
12397                        let escaped = self.escape_block_for_single_quote(content);
12398                        self.write("'");
12399                        self.write(&escaped);
12400                        self.write("'");
12401                    }
12402                }
12403            }
12404        }
12405
12406        Ok(())
12407    }
12408
12409    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12410        self.write_keyword("DROP PROCEDURE");
12411
12412        if dp.if_exists {
12413            self.write_space();
12414            self.write_keyword("IF EXISTS");
12415        }
12416
12417        self.write_space();
12418        self.generate_table(&dp.name)?;
12419
12420        if let Some(params) = &dp.parameters {
12421            self.write(" (");
12422            for (i, dt) in params.iter().enumerate() {
12423                if i > 0 {
12424                    self.write(", ");
12425                }
12426                self.generate_data_type(dt)?;
12427            }
12428            self.write(")");
12429        }
12430
12431        if dp.cascade {
12432            self.write_space();
12433            self.write_keyword("CASCADE");
12434        }
12435
12436        Ok(())
12437    }
12438
12439    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12440        self.write_keyword("CREATE");
12441
12442        if cs.or_replace {
12443            self.write_space();
12444            self.write_keyword("OR REPLACE");
12445        }
12446
12447        if cs.temporary {
12448            self.write_space();
12449            self.write_keyword("TEMPORARY");
12450        }
12451
12452        self.write_space();
12453        self.write_keyword("SEQUENCE");
12454
12455        if cs.if_not_exists {
12456            self.write_space();
12457            self.write_keyword("IF NOT EXISTS");
12458        }
12459
12460        self.write_space();
12461        self.generate_table(&cs.name)?;
12462
12463        // Output AS <type> if present
12464        if let Some(as_type) = &cs.as_type {
12465            self.write_space();
12466            self.write_keyword("AS");
12467            self.write_space();
12468            self.generate_data_type(as_type)?;
12469        }
12470
12471        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12472        if let Some(comment) = &cs.comment {
12473            self.write_space();
12474            self.write_keyword("COMMENT");
12475            self.write("=");
12476            self.generate_string_literal(comment)?;
12477        }
12478
12479        // If property_order is available, use it to preserve original order
12480        if !cs.property_order.is_empty() {
12481            for prop in &cs.property_order {
12482                match prop {
12483                    SeqPropKind::Start => {
12484                        if let Some(start) = cs.start {
12485                            self.write_space();
12486                            self.write_keyword("START WITH");
12487                            self.write(&format!(" {}", start));
12488                        }
12489                    }
12490                    SeqPropKind::Increment => {
12491                        if let Some(inc) = cs.increment {
12492                            self.write_space();
12493                            self.write_keyword("INCREMENT BY");
12494                            self.write(&format!(" {}", inc));
12495                        }
12496                    }
12497                    SeqPropKind::Minvalue => {
12498                        if let Some(min) = &cs.minvalue {
12499                            self.write_space();
12500                            match min {
12501                                SequenceBound::Value(v) => {
12502                                    self.write_keyword("MINVALUE");
12503                                    self.write(&format!(" {}", v));
12504                                }
12505                                SequenceBound::None => {
12506                                    self.write_keyword("NO MINVALUE");
12507                                }
12508                            }
12509                        }
12510                    }
12511                    SeqPropKind::Maxvalue => {
12512                        if let Some(max) = &cs.maxvalue {
12513                            self.write_space();
12514                            match max {
12515                                SequenceBound::Value(v) => {
12516                                    self.write_keyword("MAXVALUE");
12517                                    self.write(&format!(" {}", v));
12518                                }
12519                                SequenceBound::None => {
12520                                    self.write_keyword("NO MAXVALUE");
12521                                }
12522                            }
12523                        }
12524                    }
12525                    SeqPropKind::Cache => {
12526                        if let Some(cache) = cs.cache {
12527                            self.write_space();
12528                            self.write_keyword("CACHE");
12529                            self.write(&format!(" {}", cache));
12530                        }
12531                    }
12532                    SeqPropKind::NoCache => {
12533                        self.write_space();
12534                        self.write_keyword("NO CACHE");
12535                    }
12536                    SeqPropKind::NoCacheWord => {
12537                        self.write_space();
12538                        self.write_keyword("NOCACHE");
12539                    }
12540                    SeqPropKind::Cycle => {
12541                        self.write_space();
12542                        self.write_keyword("CYCLE");
12543                    }
12544                    SeqPropKind::NoCycle => {
12545                        self.write_space();
12546                        self.write_keyword("NO CYCLE");
12547                    }
12548                    SeqPropKind::NoCycleWord => {
12549                        self.write_space();
12550                        self.write_keyword("NOCYCLE");
12551                    }
12552                    SeqPropKind::OwnedBy => {
12553                        // Skip OWNED BY NONE (it's a no-op)
12554                        if !cs.owned_by_none {
12555                            if let Some(owned) = &cs.owned_by {
12556                                self.write_space();
12557                                self.write_keyword("OWNED BY");
12558                                self.write_space();
12559                                self.generate_table(owned)?;
12560                            }
12561                        }
12562                    }
12563                    SeqPropKind::Order => {
12564                        self.write_space();
12565                        self.write_keyword("ORDER");
12566                    }
12567                    SeqPropKind::NoOrder => {
12568                        self.write_space();
12569                        self.write_keyword("NOORDER");
12570                    }
12571                    SeqPropKind::Comment => {
12572                        // COMMENT is output above, before property_order iteration
12573                    }
12574                    SeqPropKind::Sharing => {
12575                        if let Some(val) = &cs.sharing {
12576                            self.write_space();
12577                            self.write(&format!("SHARING={}", val));
12578                        }
12579                    }
12580                    SeqPropKind::Keep => {
12581                        self.write_space();
12582                        self.write_keyword("KEEP");
12583                    }
12584                    SeqPropKind::NoKeep => {
12585                        self.write_space();
12586                        self.write_keyword("NOKEEP");
12587                    }
12588                    SeqPropKind::Scale => {
12589                        self.write_space();
12590                        self.write_keyword("SCALE");
12591                        if let Some(modifier) = &cs.scale_modifier {
12592                            if !modifier.is_empty() {
12593                                self.write_space();
12594                                self.write_keyword(modifier);
12595                            }
12596                        }
12597                    }
12598                    SeqPropKind::NoScale => {
12599                        self.write_space();
12600                        self.write_keyword("NOSCALE");
12601                    }
12602                    SeqPropKind::Shard => {
12603                        self.write_space();
12604                        self.write_keyword("SHARD");
12605                        if let Some(modifier) = &cs.shard_modifier {
12606                            if !modifier.is_empty() {
12607                                self.write_space();
12608                                self.write_keyword(modifier);
12609                            }
12610                        }
12611                    }
12612                    SeqPropKind::NoShard => {
12613                        self.write_space();
12614                        self.write_keyword("NOSHARD");
12615                    }
12616                    SeqPropKind::Session => {
12617                        self.write_space();
12618                        self.write_keyword("SESSION");
12619                    }
12620                    SeqPropKind::Global => {
12621                        self.write_space();
12622                        self.write_keyword("GLOBAL");
12623                    }
12624                    SeqPropKind::NoMinvalueWord => {
12625                        self.write_space();
12626                        self.write_keyword("NOMINVALUE");
12627                    }
12628                    SeqPropKind::NoMaxvalueWord => {
12629                        self.write_space();
12630                        self.write_keyword("NOMAXVALUE");
12631                    }
12632                }
12633            }
12634        } else {
12635            // Fallback: default order for backwards compatibility
12636            if let Some(inc) = cs.increment {
12637                self.write_space();
12638                self.write_keyword("INCREMENT BY");
12639                self.write(&format!(" {}", inc));
12640            }
12641
12642            if let Some(min) = &cs.minvalue {
12643                self.write_space();
12644                match min {
12645                    SequenceBound::Value(v) => {
12646                        self.write_keyword("MINVALUE");
12647                        self.write(&format!(" {}", v));
12648                    }
12649                    SequenceBound::None => {
12650                        self.write_keyword("NO MINVALUE");
12651                    }
12652                }
12653            }
12654
12655            if let Some(max) = &cs.maxvalue {
12656                self.write_space();
12657                match max {
12658                    SequenceBound::Value(v) => {
12659                        self.write_keyword("MAXVALUE");
12660                        self.write(&format!(" {}", v));
12661                    }
12662                    SequenceBound::None => {
12663                        self.write_keyword("NO MAXVALUE");
12664                    }
12665                }
12666            }
12667
12668            if let Some(start) = cs.start {
12669                self.write_space();
12670                self.write_keyword("START WITH");
12671                self.write(&format!(" {}", start));
12672            }
12673
12674            if let Some(cache) = cs.cache {
12675                self.write_space();
12676                self.write_keyword("CACHE");
12677                self.write(&format!(" {}", cache));
12678            }
12679
12680            if cs.cycle {
12681                self.write_space();
12682                self.write_keyword("CYCLE");
12683            }
12684
12685            if let Some(owned) = &cs.owned_by {
12686                self.write_space();
12687                self.write_keyword("OWNED BY");
12688                self.write_space();
12689                self.generate_table(owned)?;
12690            }
12691        }
12692
12693        Ok(())
12694    }
12695
12696    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12697        self.write_keyword("DROP SEQUENCE");
12698
12699        if ds.if_exists {
12700            self.write_space();
12701            self.write_keyword("IF EXISTS");
12702        }
12703
12704        self.write_space();
12705        self.generate_table(&ds.name)?;
12706
12707        if ds.cascade {
12708            self.write_space();
12709            self.write_keyword("CASCADE");
12710        }
12711
12712        Ok(())
12713    }
12714
12715    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12716        self.write_keyword("ALTER SEQUENCE");
12717
12718        if als.if_exists {
12719            self.write_space();
12720            self.write_keyword("IF EXISTS");
12721        }
12722
12723        self.write_space();
12724        self.generate_table(&als.name)?;
12725
12726        if let Some(inc) = als.increment {
12727            self.write_space();
12728            self.write_keyword("INCREMENT BY");
12729            self.write(&format!(" {}", inc));
12730        }
12731
12732        if let Some(min) = &als.minvalue {
12733            self.write_space();
12734            match min {
12735                SequenceBound::Value(v) => {
12736                    self.write_keyword("MINVALUE");
12737                    self.write(&format!(" {}", v));
12738                }
12739                SequenceBound::None => {
12740                    self.write_keyword("NO MINVALUE");
12741                }
12742            }
12743        }
12744
12745        if let Some(max) = &als.maxvalue {
12746            self.write_space();
12747            match max {
12748                SequenceBound::Value(v) => {
12749                    self.write_keyword("MAXVALUE");
12750                    self.write(&format!(" {}", v));
12751                }
12752                SequenceBound::None => {
12753                    self.write_keyword("NO MAXVALUE");
12754                }
12755            }
12756        }
12757
12758        if let Some(start) = als.start {
12759            self.write_space();
12760            self.write_keyword("START WITH");
12761            self.write(&format!(" {}", start));
12762        }
12763
12764        if let Some(restart) = &als.restart {
12765            self.write_space();
12766            self.write_keyword("RESTART");
12767            if let Some(val) = restart {
12768                self.write_keyword(" WITH");
12769                self.write(&format!(" {}", val));
12770            }
12771        }
12772
12773        if let Some(cache) = als.cache {
12774            self.write_space();
12775            self.write_keyword("CACHE");
12776            self.write(&format!(" {}", cache));
12777        }
12778
12779        if let Some(cycle) = als.cycle {
12780            self.write_space();
12781            if cycle {
12782                self.write_keyword("CYCLE");
12783            } else {
12784                self.write_keyword("NO CYCLE");
12785            }
12786        }
12787
12788        if let Some(owned) = &als.owned_by {
12789            self.write_space();
12790            self.write_keyword("OWNED BY");
12791            self.write_space();
12792            if let Some(table) = owned {
12793                self.generate_table(table)?;
12794            } else {
12795                self.write_keyword("NONE");
12796            }
12797        }
12798
12799        Ok(())
12800    }
12801
12802    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12803        self.write_keyword("CREATE");
12804
12805        if ct.or_replace {
12806            self.write_space();
12807            self.write_keyword("OR REPLACE");
12808        }
12809
12810        if ct.constraint {
12811            self.write_space();
12812            self.write_keyword("CONSTRAINT");
12813        }
12814
12815        self.write_space();
12816        self.write_keyword("TRIGGER");
12817        self.write_space();
12818        self.generate_identifier(&ct.name)?;
12819
12820        self.write_space();
12821        match ct.timing {
12822            TriggerTiming::Before => self.write_keyword("BEFORE"),
12823            TriggerTiming::After => self.write_keyword("AFTER"),
12824            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12825        }
12826
12827        // Events
12828        for (i, event) in ct.events.iter().enumerate() {
12829            if i > 0 {
12830                self.write_keyword(" OR");
12831            }
12832            self.write_space();
12833            match event {
12834                TriggerEvent::Insert => self.write_keyword("INSERT"),
12835                TriggerEvent::Update(cols) => {
12836                    self.write_keyword("UPDATE");
12837                    if let Some(cols) = cols {
12838                        self.write_space();
12839                        self.write_keyword("OF");
12840                        for (j, col) in cols.iter().enumerate() {
12841                            if j > 0 {
12842                                self.write(",");
12843                            }
12844                            self.write_space();
12845                            self.generate_identifier(col)?;
12846                        }
12847                    }
12848                }
12849                TriggerEvent::Delete => self.write_keyword("DELETE"),
12850                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12851            }
12852        }
12853
12854        self.write_space();
12855        self.write_keyword("ON");
12856        self.write_space();
12857        self.generate_table(&ct.table)?;
12858
12859        // Referencing clause
12860        if let Some(ref_clause) = &ct.referencing {
12861            self.write_space();
12862            self.write_keyword("REFERENCING");
12863            if let Some(old_table) = &ref_clause.old_table {
12864                self.write_space();
12865                self.write_keyword("OLD TABLE AS");
12866                self.write_space();
12867                self.generate_identifier(old_table)?;
12868            }
12869            if let Some(new_table) = &ref_clause.new_table {
12870                self.write_space();
12871                self.write_keyword("NEW TABLE AS");
12872                self.write_space();
12873                self.generate_identifier(new_table)?;
12874            }
12875            if let Some(old_row) = &ref_clause.old_row {
12876                self.write_space();
12877                self.write_keyword("OLD ROW AS");
12878                self.write_space();
12879                self.generate_identifier(old_row)?;
12880            }
12881            if let Some(new_row) = &ref_clause.new_row {
12882                self.write_space();
12883                self.write_keyword("NEW ROW AS");
12884                self.write_space();
12885                self.generate_identifier(new_row)?;
12886            }
12887        }
12888
12889        // Deferrable options for constraint triggers (must come before FOR EACH)
12890        if let Some(deferrable) = ct.deferrable {
12891            self.write_space();
12892            if deferrable {
12893                self.write_keyword("DEFERRABLE");
12894            } else {
12895                self.write_keyword("NOT DEFERRABLE");
12896            }
12897        }
12898
12899        if let Some(initially) = ct.initially_deferred {
12900            self.write_space();
12901            self.write_keyword("INITIALLY");
12902            self.write_space();
12903            if initially {
12904                self.write_keyword("DEFERRED");
12905            } else {
12906                self.write_keyword("IMMEDIATE");
12907            }
12908        }
12909
12910        self.write_space();
12911        self.write_keyword("FOR EACH");
12912        self.write_space();
12913        match ct.for_each {
12914            TriggerForEach::Row => self.write_keyword("ROW"),
12915            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12916        }
12917
12918        // When clause
12919        if let Some(when) = &ct.when {
12920            self.write_space();
12921            self.write_keyword("WHEN");
12922            self.write(" (");
12923            self.generate_expression(when)?;
12924            self.write(")");
12925        }
12926
12927        // Body
12928        self.write_space();
12929        match &ct.body {
12930            TriggerBody::Execute { function, args } => {
12931                self.write_keyword("EXECUTE FUNCTION");
12932                self.write_space();
12933                self.generate_table(function)?;
12934                self.write("(");
12935                for (i, arg) in args.iter().enumerate() {
12936                    if i > 0 {
12937                        self.write(", ");
12938                    }
12939                    self.generate_expression(arg)?;
12940                }
12941                self.write(")");
12942            }
12943            TriggerBody::Block(block) => {
12944                self.write_keyword("BEGIN");
12945                self.write_space();
12946                self.write(block);
12947                self.write_space();
12948                self.write_keyword("END");
12949            }
12950        }
12951
12952        Ok(())
12953    }
12954
12955    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12956        self.write_keyword("DROP TRIGGER");
12957
12958        if dt.if_exists {
12959            self.write_space();
12960            self.write_keyword("IF EXISTS");
12961        }
12962
12963        self.write_space();
12964        self.generate_identifier(&dt.name)?;
12965
12966        if let Some(table) = &dt.table {
12967            self.write_space();
12968            self.write_keyword("ON");
12969            self.write_space();
12970            self.generate_table(table)?;
12971        }
12972
12973        if dt.cascade {
12974            self.write_space();
12975            self.write_keyword("CASCADE");
12976        }
12977
12978        Ok(())
12979    }
12980
12981    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12982        self.write_keyword("CREATE TYPE");
12983
12984        if ct.if_not_exists {
12985            self.write_space();
12986            self.write_keyword("IF NOT EXISTS");
12987        }
12988
12989        self.write_space();
12990        self.generate_table(&ct.name)?;
12991
12992        self.write_space();
12993        self.write_keyword("AS");
12994        self.write_space();
12995
12996        match &ct.definition {
12997            TypeDefinition::Enum(values) => {
12998                self.write_keyword("ENUM");
12999                self.write(" (");
13000                for (i, val) in values.iter().enumerate() {
13001                    if i > 0 {
13002                        self.write(", ");
13003                    }
13004                    self.write(&format!("'{}'", val));
13005                }
13006                self.write(")");
13007            }
13008            TypeDefinition::Composite(attrs) => {
13009                self.write("(");
13010                for (i, attr) in attrs.iter().enumerate() {
13011                    if i > 0 {
13012                        self.write(", ");
13013                    }
13014                    self.generate_identifier(&attr.name)?;
13015                    self.write_space();
13016                    self.generate_data_type(&attr.data_type)?;
13017                    if let Some(collate) = &attr.collate {
13018                        self.write_space();
13019                        self.write_keyword("COLLATE");
13020                        self.write_space();
13021                        self.generate_identifier(collate)?;
13022                    }
13023                }
13024                self.write(")");
13025            }
13026            TypeDefinition::Range {
13027                subtype,
13028                subtype_diff,
13029                canonical,
13030            } => {
13031                self.write_keyword("RANGE");
13032                self.write(" (");
13033                self.write_keyword("SUBTYPE");
13034                self.write(" = ");
13035                self.generate_data_type(subtype)?;
13036                if let Some(diff) = subtype_diff {
13037                    self.write(", ");
13038                    self.write_keyword("SUBTYPE_DIFF");
13039                    self.write(" = ");
13040                    self.write(diff);
13041                }
13042                if let Some(canon) = canonical {
13043                    self.write(", ");
13044                    self.write_keyword("CANONICAL");
13045                    self.write(" = ");
13046                    self.write(canon);
13047                }
13048                self.write(")");
13049            }
13050            TypeDefinition::Base {
13051                input,
13052                output,
13053                internallength,
13054            } => {
13055                self.write("(");
13056                self.write_keyword("INPUT");
13057                self.write(" = ");
13058                self.write(input);
13059                self.write(", ");
13060                self.write_keyword("OUTPUT");
13061                self.write(" = ");
13062                self.write(output);
13063                if let Some(len) = internallength {
13064                    self.write(", ");
13065                    self.write_keyword("INTERNALLENGTH");
13066                    self.write(" = ");
13067                    self.write(&len.to_string());
13068                }
13069                self.write(")");
13070            }
13071            TypeDefinition::Domain {
13072                base_type,
13073                default,
13074                constraints,
13075            } => {
13076                self.generate_data_type(base_type)?;
13077                if let Some(def) = default {
13078                    self.write_space();
13079                    self.write_keyword("DEFAULT");
13080                    self.write_space();
13081                    self.generate_expression(def)?;
13082                }
13083                for constr in constraints {
13084                    self.write_space();
13085                    if let Some(name) = &constr.name {
13086                        self.write_keyword("CONSTRAINT");
13087                        self.write_space();
13088                        self.generate_identifier(name)?;
13089                        self.write_space();
13090                    }
13091                    self.write_keyword("CHECK");
13092                    self.write(" (");
13093                    self.generate_expression(&constr.check)?;
13094                    self.write(")");
13095                }
13096            }
13097        }
13098
13099        Ok(())
13100    }
13101
13102    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13103        self.write_keyword("DROP TYPE");
13104
13105        if dt.if_exists {
13106            self.write_space();
13107            self.write_keyword("IF EXISTS");
13108        }
13109
13110        self.write_space();
13111        self.generate_table(&dt.name)?;
13112
13113        if dt.cascade {
13114            self.write_space();
13115            self.write_keyword("CASCADE");
13116        }
13117
13118        Ok(())
13119    }
13120
13121    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13122        // Athena: DESCRIBE uses Hive engine (backticks)
13123        let saved_athena_hive_context = self.athena_hive_context;
13124        if matches!(
13125            self.config.dialect,
13126            Some(crate::dialects::DialectType::Athena)
13127        ) {
13128            self.athena_hive_context = true;
13129        }
13130
13131        // Output leading comments before DESCRIBE
13132        for comment in &d.leading_comments {
13133            self.write_formatted_comment(comment);
13134            self.write(" ");
13135        }
13136
13137        self.write_keyword("DESCRIBE");
13138
13139        if d.extended {
13140            self.write_space();
13141            self.write_keyword("EXTENDED");
13142        } else if d.formatted {
13143            self.write_space();
13144            self.write_keyword("FORMATTED");
13145        }
13146
13147        // Output style like ANALYZE, HISTORY
13148        if let Some(ref style) = d.style {
13149            self.write_space();
13150            self.write_keyword(style);
13151        }
13152
13153        // Handle object kind (TABLE, VIEW) based on dialect
13154        let should_output_kind = match self.config.dialect {
13155            // Spark doesn't use TABLE/VIEW after DESCRIBE
13156            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13157                false
13158            }
13159            // Snowflake always includes TABLE
13160            Some(DialectType::Snowflake) => true,
13161            _ => d.kind.is_some(),
13162        };
13163        if should_output_kind {
13164            if let Some(ref kind) = d.kind {
13165                self.write_space();
13166                self.write_keyword(kind);
13167            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13168                self.write_space();
13169                self.write_keyword("TABLE");
13170            }
13171        }
13172
13173        self.write_space();
13174        self.generate_expression(&d.target)?;
13175
13176        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13177        if let Some(ref partition) = d.partition {
13178            self.write_space();
13179            self.generate_expression(partition)?;
13180        }
13181
13182        // Databricks: AS JSON
13183        if d.as_json {
13184            self.write_space();
13185            self.write_keyword("AS JSON");
13186        }
13187
13188        // Output properties like type=stage
13189        for (name, value) in &d.properties {
13190            self.write_space();
13191            self.write(name);
13192            self.write("=");
13193            self.write(value);
13194        }
13195
13196        // Restore Athena Hive context
13197        self.athena_hive_context = saved_athena_hive_context;
13198
13199        Ok(())
13200    }
13201
13202    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13203    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13204    fn generate_show(&mut self, s: &Show) -> Result<()> {
13205        self.write_keyword("SHOW");
13206        self.write_space();
13207
13208        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13209        // where TERSE is syntactically valid but has no effect on output
13210        let show_terse = s.terse
13211            && !matches!(
13212                s.this.as_str(),
13213                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13214            );
13215        if show_terse {
13216            self.write_keyword("TERSE");
13217            self.write_space();
13218        }
13219
13220        // Object type (USERS, TABLES, DATABASES, etc.)
13221        self.write_keyword(&s.this);
13222
13223        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13224        if let Some(ref target_expr) = s.target {
13225            self.write_space();
13226            self.generate_expression(target_expr)?;
13227        }
13228
13229        // HISTORY keyword
13230        if s.history {
13231            self.write_space();
13232            self.write_keyword("HISTORY");
13233        }
13234
13235        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13236        if let Some(ref for_target) = s.for_target {
13237            self.write_space();
13238            self.write_keyword("FOR");
13239            self.write_space();
13240            self.generate_expression(for_target)?;
13241        }
13242
13243        // Determine ordering based on dialect:
13244        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13245        // - MySQL: IN, FROM, LIKE (when FROM is present)
13246        use crate::dialects::DialectType;
13247        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13248
13249        if !is_snowflake && s.from.is_some() {
13250            // MySQL ordering: IN, FROM, LIKE
13251
13252            // IN scope_kind [scope]
13253            if let Some(ref scope_kind) = s.scope_kind {
13254                self.write_space();
13255                self.write_keyword("IN");
13256                self.write_space();
13257                self.write_keyword(scope_kind);
13258                if let Some(ref scope) = s.scope {
13259                    self.write_space();
13260                    self.generate_expression(scope)?;
13261                }
13262            } else if let Some(ref scope) = s.scope {
13263                self.write_space();
13264                self.write_keyword("IN");
13265                self.write_space();
13266                self.generate_expression(scope)?;
13267            }
13268
13269            // FROM clause
13270            if let Some(ref from) = s.from {
13271                self.write_space();
13272                self.write_keyword("FROM");
13273                self.write_space();
13274                self.generate_expression(from)?;
13275            }
13276
13277            // Second FROM clause (db name)
13278            if let Some(ref db) = s.db {
13279                self.write_space();
13280                self.write_keyword("FROM");
13281                self.write_space();
13282                self.generate_expression(db)?;
13283            }
13284
13285            // LIKE pattern
13286            if let Some(ref like) = s.like {
13287                self.write_space();
13288                self.write_keyword("LIKE");
13289                self.write_space();
13290                self.generate_expression(like)?;
13291            }
13292        } else {
13293            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13294
13295            // LIKE pattern
13296            if let Some(ref like) = s.like {
13297                self.write_space();
13298                self.write_keyword("LIKE");
13299                self.write_space();
13300                self.generate_expression(like)?;
13301            }
13302
13303            // IN scope_kind [scope]
13304            if let Some(ref scope_kind) = s.scope_kind {
13305                self.write_space();
13306                self.write_keyword("IN");
13307                self.write_space();
13308                self.write_keyword(scope_kind);
13309                if let Some(ref scope) = s.scope {
13310                    self.write_space();
13311                    self.generate_expression(scope)?;
13312                }
13313            } else if let Some(ref scope) = s.scope {
13314                self.write_space();
13315                self.write_keyword("IN");
13316                self.write_space();
13317                self.generate_expression(scope)?;
13318            }
13319        }
13320
13321        // STARTS WITH pattern
13322        if let Some(ref starts_with) = s.starts_with {
13323            self.write_space();
13324            self.write_keyword("STARTS WITH");
13325            self.write_space();
13326            self.generate_expression(starts_with)?;
13327        }
13328
13329        // LIMIT clause
13330        if let Some(ref limit) = s.limit {
13331            self.write_space();
13332            self.generate_limit(limit)?;
13333        }
13334
13335        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13336        if is_snowflake {
13337            if let Some(ref from) = s.from {
13338                self.write_space();
13339                self.write_keyword("FROM");
13340                self.write_space();
13341                self.generate_expression(from)?;
13342            }
13343        }
13344
13345        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13346        if let Some(ref where_clause) = s.where_clause {
13347            self.write_space();
13348            self.write_keyword("WHERE");
13349            self.write_space();
13350            self.generate_expression(where_clause)?;
13351        }
13352
13353        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13354        if let Some(is_mutex) = s.mutex {
13355            self.write_space();
13356            if is_mutex {
13357                self.write_keyword("MUTEX");
13358            } else {
13359                self.write_keyword("STATUS");
13360            }
13361        }
13362
13363        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13364        if !s.privileges.is_empty() {
13365            self.write_space();
13366            self.write_keyword("WITH PRIVILEGES");
13367            self.write_space();
13368            for (i, priv_name) in s.privileges.iter().enumerate() {
13369                if i > 0 {
13370                    self.write(", ");
13371                }
13372                self.write_keyword(priv_name);
13373            }
13374        }
13375
13376        Ok(())
13377    }
13378
13379    // ==================== End DDL Generation ====================
13380
13381    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13382        use crate::dialects::DialectType;
13383        match lit {
13384            Literal::String(s) => {
13385                self.generate_string_literal(s)?;
13386            }
13387            Literal::Number(n) => {
13388                if matches!(self.config.dialect, Some(DialectType::MySQL))
13389                    && n.len() > 2
13390                    && (n.starts_with("0x") || n.starts_with("0X"))
13391                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13392                {
13393                    return self.generate_identifier(&Identifier {
13394                        name: n.clone(),
13395                        quoted: true,
13396                        trailing_comments: Vec::new(),
13397                        span: None,
13398                    });
13399                }
13400                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13401                // for dialects that don't support them (MySQL interprets as identifier).
13402                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13403                let n = if n.contains('_')
13404                    && !matches!(
13405                        self.config.dialect,
13406                        Some(DialectType::ClickHouse)
13407                            | Some(DialectType::DuckDB)
13408                            | Some(DialectType::PostgreSQL)
13409                            | Some(DialectType::Hive)
13410                            | Some(DialectType::Spark)
13411                            | Some(DialectType::Databricks)
13412                    ) {
13413                    std::borrow::Cow::Owned(n.replace('_', ""))
13414                } else {
13415                    std::borrow::Cow::Borrowed(n.as_str())
13416                };
13417                // Normalize numbers starting with decimal point to have leading zero
13418                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13419                if n.starts_with('.') {
13420                    self.write("0");
13421                    self.write(&n);
13422                } else if n.starts_with("-.") {
13423                    // Handle negative numbers like -.25 -> -0.25
13424                    self.write("-0");
13425                    self.write(&n[1..]);
13426                } else {
13427                    self.write(&n);
13428                }
13429            }
13430            Literal::HexString(h) => {
13431                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13432                match self.config.dialect {
13433                    Some(DialectType::Spark)
13434                    | Some(DialectType::Databricks)
13435                    | Some(DialectType::Teradata) => self.write("X'"),
13436                    _ => self.write("x'"),
13437                }
13438                self.write(h);
13439                self.write("'");
13440            }
13441            Literal::HexNumber(h) => {
13442                // Hex number (0xA) - integer in hex notation (from BigQuery)
13443                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13444                // For other dialects, convert to decimal integer
13445                match self.config.dialect {
13446                    Some(DialectType::BigQuery)
13447                    | Some(DialectType::TSQL)
13448                    | Some(DialectType::Fabric) => {
13449                        self.write("0x");
13450                        self.write(h);
13451                    }
13452                    _ => {
13453                        // Convert hex to decimal
13454                        if let Ok(val) = u64::from_str_radix(h, 16) {
13455                            self.write(&val.to_string());
13456                        } else {
13457                            // Fallback: keep as 0x notation
13458                            self.write("0x");
13459                            self.write(h);
13460                        }
13461                    }
13462                }
13463            }
13464            Literal::BitString(b) => {
13465                // Bit string B'0101...'
13466                self.write("B'");
13467                self.write(b);
13468                self.write("'");
13469            }
13470            Literal::ByteString(b) => {
13471                // Byte string b'...' (BigQuery style)
13472                self.write("b'");
13473                // Escape special characters for output
13474                self.write_escaped_byte_string(b);
13475                self.write("'");
13476            }
13477            Literal::NationalString(s) => {
13478                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13479                // Other dialects strip the N prefix and output as regular string
13480                let keep_n_prefix = matches!(
13481                    self.config.dialect,
13482                    Some(DialectType::TSQL)
13483                        | Some(DialectType::Oracle)
13484                        | Some(DialectType::MySQL)
13485                        | None
13486                );
13487                if keep_n_prefix {
13488                    self.write("N'");
13489                } else {
13490                    self.write("'");
13491                }
13492                self.write(s);
13493                self.write("'");
13494            }
13495            Literal::Date(d) => {
13496                self.generate_date_literal(d)?;
13497            }
13498            Literal::Time(t) => {
13499                self.generate_time_literal(t)?;
13500            }
13501            Literal::Timestamp(ts) => {
13502                self.generate_timestamp_literal(ts)?;
13503            }
13504            Literal::Datetime(dt) => {
13505                self.generate_datetime_literal(dt)?;
13506            }
13507            Literal::TripleQuotedString(s, _quote_char) => {
13508                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13509                if matches!(
13510                    self.config.dialect,
13511                    Some(crate::dialects::DialectType::BigQuery)
13512                        | Some(crate::dialects::DialectType::DuckDB)
13513                        | Some(crate::dialects::DialectType::Snowflake)
13514                        | Some(crate::dialects::DialectType::Spark)
13515                        | Some(crate::dialects::DialectType::Hive)
13516                        | Some(crate::dialects::DialectType::Presto)
13517                        | Some(crate::dialects::DialectType::Trino)
13518                        | Some(crate::dialects::DialectType::PostgreSQL)
13519                        | Some(crate::dialects::DialectType::MySQL)
13520                        | Some(crate::dialects::DialectType::Redshift)
13521                        | Some(crate::dialects::DialectType::TSQL)
13522                        | Some(crate::dialects::DialectType::Oracle)
13523                        | Some(crate::dialects::DialectType::ClickHouse)
13524                        | Some(crate::dialects::DialectType::Databricks)
13525                        | Some(crate::dialects::DialectType::SQLite)
13526                ) {
13527                    self.generate_string_literal(s)?;
13528                } else {
13529                    // Preserve triple-quoted string syntax for generic/unknown dialects
13530                    let quotes = format!("{0}{0}{0}", _quote_char);
13531                    self.write(&quotes);
13532                    self.write(s);
13533                    self.write(&quotes);
13534                }
13535            }
13536            Literal::EscapeString(s) => {
13537                // PostgreSQL escape string: e'...' or E'...'
13538                // Token text format is "e:content" or "E:content"
13539                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13540                use crate::dialects::DialectType;
13541                let content = if let Some(c) = s.strip_prefix("e:") {
13542                    c
13543                } else if let Some(c) = s.strip_prefix("E:") {
13544                    c
13545                } else {
13546                    s.as_str()
13547                };
13548
13549                // MySQL: output the content without quotes or prefix
13550                if matches!(
13551                    self.config.dialect,
13552                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13553                ) {
13554                    self.write(content);
13555                } else {
13556                    // Some dialects use lowercase e' prefix
13557                    let prefix = if matches!(
13558                        self.config.dialect,
13559                        Some(DialectType::SingleStore)
13560                            | Some(DialectType::DuckDB)
13561                            | Some(DialectType::PostgreSQL)
13562                            | Some(DialectType::CockroachDB)
13563                            | Some(DialectType::Materialize)
13564                            | Some(DialectType::RisingWave)
13565                    ) {
13566                        "e'"
13567                    } else {
13568                        "E'"
13569                    };
13570
13571                    // Normalize \' to '' for output
13572                    let normalized = content.replace("\\'", "''");
13573                    self.write(prefix);
13574                    self.write(&normalized);
13575                    self.write("'");
13576                }
13577            }
13578            Literal::DollarString(s) => {
13579                // Convert dollar-quoted strings to single-quoted strings
13580                // (like Python sqlglot's rawstring_sql)
13581                use crate::dialects::DialectType;
13582                // Extract content from tag\x00content format
13583                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13584                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13585                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13586                // Step 2: Determine quote escaping style
13587                // Snowflake: ' -> \' (backslash escape)
13588                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13589                let use_backslash_quote =
13590                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13591
13592                let mut escaped = String::with_capacity(content.len() + 4);
13593                for ch in content.chars() {
13594                    if escape_backslash && ch == '\\' {
13595                        // Escape backslash first (before quote escaping)
13596                        escaped.push('\\');
13597                        escaped.push('\\');
13598                    } else if ch == '\'' {
13599                        if use_backslash_quote {
13600                            escaped.push('\\');
13601                            escaped.push('\'');
13602                        } else {
13603                            escaped.push('\'');
13604                            escaped.push('\'');
13605                        }
13606                    } else {
13607                        escaped.push(ch);
13608                    }
13609                }
13610                self.write("'");
13611                self.write(&escaped);
13612                self.write("'");
13613            }
13614            Literal::RawString(s) => {
13615                // Raw strings (r"..." or r'...') contain literal backslashes.
13616                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13617                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13618                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13619                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13620                use crate::dialects::DialectType;
13621
13622                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13623                let escape_backslash = matches!(
13624                    self.config.dialect,
13625                    Some(DialectType::BigQuery)
13626                        | Some(DialectType::MySQL)
13627                        | Some(DialectType::SingleStore)
13628                        | Some(DialectType::TiDB)
13629                        | Some(DialectType::Hive)
13630                        | Some(DialectType::Spark)
13631                        | Some(DialectType::Databricks)
13632                        | Some(DialectType::Drill)
13633                        | Some(DialectType::Snowflake)
13634                        | Some(DialectType::Redshift)
13635                        | Some(DialectType::ClickHouse)
13636                );
13637
13638                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13639                // These escape quotes as \' instead of ''
13640                let backslash_escapes_quote = matches!(
13641                    self.config.dialect,
13642                    Some(DialectType::BigQuery)
13643                        | Some(DialectType::Hive)
13644                        | Some(DialectType::Spark)
13645                        | Some(DialectType::Databricks)
13646                        | Some(DialectType::Drill)
13647                        | Some(DialectType::Snowflake)
13648                        | Some(DialectType::Redshift)
13649                );
13650
13651                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13652                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13653                let supports_escape_sequences = escape_backslash;
13654
13655                let mut escaped = String::with_capacity(s.len() + 4);
13656                for ch in s.chars() {
13657                    if escape_backslash && ch == '\\' {
13658                        // Double the backslash for the target dialect
13659                        escaped.push('\\');
13660                        escaped.push('\\');
13661                    } else if ch == '\'' {
13662                        if backslash_escapes_quote {
13663                            // Use backslash to escape the quote: \'
13664                            escaped.push('\\');
13665                            escaped.push('\'');
13666                        } else {
13667                            // Use SQL standard quote doubling: ''
13668                            escaped.push('\'');
13669                            escaped.push('\'');
13670                        }
13671                    } else if supports_escape_sequences {
13672                        // Apply ESCAPED_SEQUENCES mapping for special chars
13673                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13674                        match ch {
13675                            '\n' => {
13676                                escaped.push('\\');
13677                                escaped.push('n');
13678                            }
13679                            '\r' => {
13680                                escaped.push('\\');
13681                                escaped.push('r');
13682                            }
13683                            '\t' => {
13684                                escaped.push('\\');
13685                                escaped.push('t');
13686                            }
13687                            '\x07' => {
13688                                escaped.push('\\');
13689                                escaped.push('a');
13690                            }
13691                            '\x08' => {
13692                                escaped.push('\\');
13693                                escaped.push('b');
13694                            }
13695                            '\x0C' => {
13696                                escaped.push('\\');
13697                                escaped.push('f');
13698                            }
13699                            '\x0B' => {
13700                                escaped.push('\\');
13701                                escaped.push('v');
13702                            }
13703                            _ => escaped.push(ch),
13704                        }
13705                    } else {
13706                        escaped.push(ch);
13707                    }
13708                }
13709                self.write("'");
13710                self.write(&escaped);
13711                self.write("'");
13712            }
13713        }
13714        Ok(())
13715    }
13716
13717    /// Generate a DATE literal with dialect-specific formatting
13718    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13719        use crate::dialects::DialectType;
13720
13721        match self.config.dialect {
13722            // SQL Server uses CONVERT or CAST
13723            Some(DialectType::TSQL) => {
13724                self.write("CAST('");
13725                self.write(d);
13726                self.write("' AS DATE)");
13727            }
13728            // BigQuery uses CAST syntax for type literals
13729            // DATE 'value' -> CAST('value' AS DATE)
13730            Some(DialectType::BigQuery) => {
13731                self.write("CAST('");
13732                self.write(d);
13733                self.write("' AS DATE)");
13734            }
13735            // Exasol uses CAST syntax for DATE literals
13736            // DATE 'value' -> CAST('value' AS DATE)
13737            Some(DialectType::Exasol) => {
13738                self.write("CAST('");
13739                self.write(d);
13740                self.write("' AS DATE)");
13741            }
13742            // Snowflake uses CAST syntax for DATE literals
13743            // DATE 'value' -> CAST('value' AS DATE)
13744            Some(DialectType::Snowflake) => {
13745                self.write("CAST('");
13746                self.write(d);
13747                self.write("' AS DATE)");
13748            }
13749            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13750            Some(DialectType::PostgreSQL)
13751            | Some(DialectType::MySQL)
13752            | Some(DialectType::SingleStore)
13753            | Some(DialectType::TiDB)
13754            | Some(DialectType::Redshift) => {
13755                self.write("CAST('");
13756                self.write(d);
13757                self.write("' AS DATE)");
13758            }
13759            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13760            Some(DialectType::DuckDB)
13761            | Some(DialectType::Presto)
13762            | Some(DialectType::Trino)
13763            | Some(DialectType::Athena)
13764            | Some(DialectType::Spark)
13765            | Some(DialectType::Databricks)
13766            | Some(DialectType::Hive) => {
13767                self.write("CAST('");
13768                self.write(d);
13769                self.write("' AS DATE)");
13770            }
13771            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13772            Some(DialectType::Oracle) => {
13773                self.write("TO_DATE('");
13774                self.write(d);
13775                self.write("', 'YYYY-MM-DD')");
13776            }
13777            // Standard SQL: DATE '...'
13778            _ => {
13779                self.write_keyword("DATE");
13780                self.write(" '");
13781                self.write(d);
13782                self.write("'");
13783            }
13784        }
13785        Ok(())
13786    }
13787
13788    /// Generate a TIME literal with dialect-specific formatting
13789    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13790        use crate::dialects::DialectType;
13791
13792        match self.config.dialect {
13793            // SQL Server uses CONVERT or CAST
13794            Some(DialectType::TSQL) => {
13795                self.write("CAST('");
13796                self.write(t);
13797                self.write("' AS TIME)");
13798            }
13799            // Standard SQL: TIME '...'
13800            _ => {
13801                self.write_keyword("TIME");
13802                self.write(" '");
13803                self.write(t);
13804                self.write("'");
13805            }
13806        }
13807        Ok(())
13808    }
13809
13810    /// Generate a date expression for Dremio, converting DATE literals to CAST
13811    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13812        use crate::expressions::Literal;
13813
13814        match expr {
13815            Expression::Literal(Literal::Date(d)) => {
13816                // DATE 'value' -> CAST('value' AS DATE)
13817                self.write("CAST('");
13818                self.write(d);
13819                self.write("' AS DATE)");
13820            }
13821            _ => {
13822                // For all other expressions, generate normally
13823                self.generate_expression(expr)?;
13824            }
13825        }
13826        Ok(())
13827    }
13828
13829    /// Generate a TIMESTAMP literal with dialect-specific formatting
13830    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13831        use crate::dialects::DialectType;
13832
13833        match self.config.dialect {
13834            // SQL Server uses CONVERT or CAST
13835            Some(DialectType::TSQL) => {
13836                self.write("CAST('");
13837                self.write(ts);
13838                self.write("' AS DATETIME2)");
13839            }
13840            // BigQuery uses CAST syntax for type literals
13841            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13842            Some(DialectType::BigQuery) => {
13843                self.write("CAST('");
13844                self.write(ts);
13845                self.write("' AS TIMESTAMP)");
13846            }
13847            // Snowflake uses CAST syntax for TIMESTAMP literals
13848            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13849            Some(DialectType::Snowflake) => {
13850                self.write("CAST('");
13851                self.write(ts);
13852                self.write("' AS TIMESTAMP)");
13853            }
13854            // Dremio uses CAST syntax for TIMESTAMP literals
13855            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13856            Some(DialectType::Dremio) => {
13857                self.write("CAST('");
13858                self.write(ts);
13859                self.write("' AS TIMESTAMP)");
13860            }
13861            // Exasol uses CAST syntax for TIMESTAMP literals
13862            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13863            Some(DialectType::Exasol) => {
13864                self.write("CAST('");
13865                self.write(ts);
13866                self.write("' AS TIMESTAMP)");
13867            }
13868            // Oracle prefers TO_TIMESTAMP function call
13869            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13870            Some(DialectType::Oracle) => {
13871                self.write("TO_TIMESTAMP('");
13872                self.write(ts);
13873                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13874            }
13875            // Presto/Trino: always use CAST for TIMESTAMP literals
13876            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13877                if Self::timestamp_has_timezone(ts) {
13878                    self.write("CAST('");
13879                    self.write(ts);
13880                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13881                } else {
13882                    self.write("CAST('");
13883                    self.write(ts);
13884                    self.write("' AS TIMESTAMP)");
13885                }
13886            }
13887            // ClickHouse: CAST('...' AS Nullable(DateTime))
13888            Some(DialectType::ClickHouse) => {
13889                self.write("CAST('");
13890                self.write(ts);
13891                self.write("' AS Nullable(DateTime))");
13892            }
13893            // Spark: CAST('...' AS TIMESTAMP)
13894            Some(DialectType::Spark) => {
13895                self.write("CAST('");
13896                self.write(ts);
13897                self.write("' AS TIMESTAMP)");
13898            }
13899            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13900            // but TIMESTAMP '...' for special values like 'epoch'
13901            Some(DialectType::Redshift) => {
13902                if ts == "epoch" {
13903                    self.write_keyword("TIMESTAMP");
13904                    self.write(" '");
13905                    self.write(ts);
13906                    self.write("'");
13907                } else {
13908                    self.write("CAST('");
13909                    self.write(ts);
13910                    self.write("' AS TIMESTAMP)");
13911                }
13912            }
13913            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13914            Some(DialectType::PostgreSQL)
13915            | Some(DialectType::Hive)
13916            | Some(DialectType::SQLite)
13917            | Some(DialectType::DuckDB)
13918            | Some(DialectType::Athena)
13919            | Some(DialectType::Drill)
13920            | Some(DialectType::Teradata) => {
13921                self.write("CAST('");
13922                self.write(ts);
13923                self.write("' AS TIMESTAMP)");
13924            }
13925            // MySQL/StarRocks: CAST('...' AS DATETIME)
13926            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13927                self.write("CAST('");
13928                self.write(ts);
13929                self.write("' AS DATETIME)");
13930            }
13931            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13932            Some(DialectType::Databricks) => {
13933                self.write("CAST('");
13934                self.write(ts);
13935                self.write("' AS TIMESTAMP_NTZ)");
13936            }
13937            // Standard SQL: TIMESTAMP '...'
13938            _ => {
13939                self.write_keyword("TIMESTAMP");
13940                self.write(" '");
13941                self.write(ts);
13942                self.write("'");
13943            }
13944        }
13945        Ok(())
13946    }
13947
13948    /// Check if a timestamp string contains a timezone identifier
13949    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13950    fn timestamp_has_timezone(ts: &str) -> bool {
13951        // Check for common IANA timezone patterns: Continent/City format
13952        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13953        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13954        let ts_lower = ts.to_lowercase();
13955
13956        // Check for Continent/City pattern (most common)
13957        let continent_prefixes = [
13958            "africa/",
13959            "america/",
13960            "antarctica/",
13961            "arctic/",
13962            "asia/",
13963            "atlantic/",
13964            "australia/",
13965            "europe/",
13966            "indian/",
13967            "pacific/",
13968            "etc/",
13969            "brazil/",
13970            "canada/",
13971            "chile/",
13972            "mexico/",
13973            "us/",
13974        ];
13975
13976        for prefix in &continent_prefixes {
13977            if ts_lower.contains(prefix) {
13978                return true;
13979            }
13980        }
13981
13982        // Check for standalone timezone abbreviations at the end
13983        // These typically appear after the time portion
13984        let tz_abbrevs = [
13985            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13986            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13987            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13988        ];
13989
13990        for abbrev in &tz_abbrevs {
13991            if ts_lower.ends_with(abbrev) {
13992                return true;
13993            }
13994        }
13995
13996        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13997        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
13998        // Look for pattern: space followed by + or - and digits (optionally with :)
13999        let trimmed = ts.trim();
14000        if let Some(last_space) = trimmed.rfind(' ') {
14001            let suffix = &trimmed[last_space + 1..];
14002            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14003                // Check if rest is numeric (possibly with : for hh:mm format)
14004                let rest = &suffix[1..];
14005                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14006                    return true;
14007                }
14008            }
14009        }
14010
14011        false
14012    }
14013
14014    /// Generate a DATETIME literal with dialect-specific formatting
14015    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14016        use crate::dialects::DialectType;
14017
14018        match self.config.dialect {
14019            // BigQuery uses CAST syntax for type literals
14020            // DATETIME 'value' -> CAST('value' AS DATETIME)
14021            Some(DialectType::BigQuery) => {
14022                self.write("CAST('");
14023                self.write(dt);
14024                self.write("' AS DATETIME)");
14025            }
14026            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14027            Some(DialectType::DuckDB) => {
14028                self.write("CAST('");
14029                self.write(dt);
14030                self.write("' AS TIMESTAMP)");
14031            }
14032            // DATETIME is primarily a BigQuery type
14033            // Output as DATETIME '...' for dialects that support it
14034            _ => {
14035                self.write_keyword("DATETIME");
14036                self.write(" '");
14037                self.write(dt);
14038                self.write("'");
14039            }
14040        }
14041        Ok(())
14042    }
14043
14044    /// Generate a string literal with dialect-specific escaping
14045    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14046        use crate::dialects::DialectType;
14047
14048        match self.config.dialect {
14049            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14050            // and backslash escaping for special characters like newlines
14051            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14052            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14053                // Hive/Spark use backslash escaping for quotes (\') and special chars
14054                self.write("'");
14055                for c in s.chars() {
14056                    match c {
14057                        '\'' => self.write("\\'"),
14058                        '\\' => self.write("\\\\"),
14059                        '\n' => self.write("\\n"),
14060                        '\r' => self.write("\\r"),
14061                        '\t' => self.write("\\t"),
14062                        '\0' => self.write("\\0"),
14063                        _ => self.output.push(c),
14064                    }
14065                }
14066                self.write("'");
14067            }
14068            Some(DialectType::Drill) => {
14069                // Drill uses SQL-standard quote doubling ('') for quotes,
14070                // but backslash escaping for special characters
14071                self.write("'");
14072                for c in s.chars() {
14073                    match c {
14074                        '\'' => self.write("''"),
14075                        '\\' => self.write("\\\\"),
14076                        '\n' => self.write("\\n"),
14077                        '\r' => self.write("\\r"),
14078                        '\t' => self.write("\\t"),
14079                        '\0' => self.write("\\0"),
14080                        _ => self.output.push(c),
14081                    }
14082                }
14083                self.write("'");
14084            }
14085            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14086                self.write("'");
14087                for c in s.chars() {
14088                    match c {
14089                        // MySQL uses SQL standard quote doubling
14090                        '\'' => self.write("''"),
14091                        '\\' => self.write("\\\\"),
14092                        '\n' => self.write("\\n"),
14093                        '\r' => self.write("\\r"),
14094                        '\t' => self.write("\\t"),
14095                        // sqlglot writes a literal NUL for this case
14096                        '\0' => self.output.push('\0'),
14097                        _ => self.output.push(c),
14098                    }
14099                }
14100                self.write("'");
14101            }
14102            // BigQuery: Uses backslash escaping
14103            Some(DialectType::BigQuery) => {
14104                self.write("'");
14105                for c in s.chars() {
14106                    match c {
14107                        '\'' => self.write("\\'"),
14108                        '\\' => self.write("\\\\"),
14109                        '\n' => self.write("\\n"),
14110                        '\r' => self.write("\\r"),
14111                        '\t' => self.write("\\t"),
14112                        '\0' => self.write("\\0"),
14113                        '\x07' => self.write("\\a"),
14114                        '\x08' => self.write("\\b"),
14115                        '\x0C' => self.write("\\f"),
14116                        '\x0B' => self.write("\\v"),
14117                        _ => self.output.push(c),
14118                    }
14119                }
14120                self.write("'");
14121            }
14122            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14123            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14124            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14125            Some(DialectType::Athena) => {
14126                if self.athena_hive_context {
14127                    // Hive-style: backslash escaping
14128                    self.write("'");
14129                    for c in s.chars() {
14130                        match c {
14131                            '\'' => self.write("\\'"),
14132                            '\\' => self.write("\\\\"),
14133                            '\n' => self.write("\\n"),
14134                            '\r' => self.write("\\r"),
14135                            '\t' => self.write("\\t"),
14136                            '\0' => self.write("\\0"),
14137                            _ => self.output.push(c),
14138                        }
14139                    }
14140                    self.write("'");
14141                } else {
14142                    // Trino-style: SQL-standard escaping, preserve backslashes
14143                    self.write("'");
14144                    for c in s.chars() {
14145                        match c {
14146                            '\'' => self.write("''"),
14147                            // Preserve backslashes literally (no re-escaping)
14148                            _ => self.output.push(c),
14149                        }
14150                    }
14151                    self.write("'");
14152                }
14153            }
14154            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14155            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14156            // becomes string value '\\'), so we should NOT re-escape backslashes.
14157            // We only need to escape single quotes.
14158            Some(DialectType::Snowflake) => {
14159                self.write("'");
14160                for c in s.chars() {
14161                    match c {
14162                        '\'' => self.write("\\'"),
14163                        // Backslashes are already escaped in the tokenized string, don't re-escape
14164                        // Only escape special characters that might not have been escaped
14165                        '\n' => self.write("\\n"),
14166                        '\r' => self.write("\\r"),
14167                        '\t' => self.write("\\t"),
14168                        _ => self.output.push(c),
14169                    }
14170                }
14171                self.write("'");
14172            }
14173            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14174            Some(DialectType::PostgreSQL) => {
14175                self.write("'");
14176                for c in s.chars() {
14177                    match c {
14178                        '\'' => self.write("''"),
14179                        _ => self.output.push(c),
14180                    }
14181                }
14182                self.write("'");
14183            }
14184            // Redshift: Uses backslash escaping for single quotes
14185            Some(DialectType::Redshift) => {
14186                self.write("'");
14187                for c in s.chars() {
14188                    match c {
14189                        '\'' => self.write("\\'"),
14190                        _ => self.output.push(c),
14191                    }
14192                }
14193                self.write("'");
14194            }
14195            // Oracle: Uses standard double single-quote escaping
14196            Some(DialectType::Oracle) => {
14197                self.write("'");
14198                self.write(&s.replace('\'', "''"));
14199                self.write("'");
14200            }
14201            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14202            // backslash escaping for backslashes and special characters
14203            Some(DialectType::ClickHouse) => {
14204                self.write("'");
14205                for c in s.chars() {
14206                    match c {
14207                        '\'' => self.write("''"),
14208                        '\\' => self.write("\\\\"),
14209                        '\n' => self.write("\\n"),
14210                        '\r' => self.write("\\r"),
14211                        '\t' => self.write("\\t"),
14212                        '\0' => self.write("\\0"),
14213                        '\x07' => self.write("\\a"),
14214                        '\x08' => self.write("\\b"),
14215                        '\x0C' => self.write("\\f"),
14216                        '\x0B' => self.write("\\v"),
14217                        // Non-printable characters: emit as \xNN hex escapes
14218                        c if c.is_control() || (c as u32) < 0x20 => {
14219                            let byte = c as u32;
14220                            if byte < 256 {
14221                                self.write(&format!("\\x{:02X}", byte));
14222                            } else {
14223                                self.output.push(c);
14224                            }
14225                        }
14226                        _ => self.output.push(c),
14227                    }
14228                }
14229                self.write("'");
14230            }
14231            // Default: SQL standard double single quotes (works for most dialects)
14232            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14233            _ => {
14234                self.write("'");
14235                self.write(&s.replace('\'', "''"));
14236                self.write("'");
14237            }
14238        }
14239        Ok(())
14240    }
14241
14242    /// Write a byte string with proper escaping for BigQuery-style byte literals
14243    /// Escapes characters as \xNN hex escapes where needed
14244    fn write_escaped_byte_string(&mut self, s: &str) {
14245        for c in s.chars() {
14246            match c {
14247                // Escape single quotes
14248                '\'' => self.write("\\'"),
14249                // Escape backslashes
14250                '\\' => self.write("\\\\"),
14251                // Keep all printable characters (including non-ASCII) as-is
14252                _ if !c.is_control() => self.output.push(c),
14253                // Escape control characters as hex
14254                _ => {
14255                    let byte = c as u32;
14256                    if byte < 256 {
14257                        self.write(&format!("\\x{:02x}", byte));
14258                    } else {
14259                        // For unicode characters, write each UTF-8 byte
14260                        for b in c.to_string().as_bytes() {
14261                            self.write(&format!("\\x{:02x}", b));
14262                        }
14263                    }
14264                }
14265            }
14266        }
14267    }
14268
14269    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14270        use crate::dialects::DialectType;
14271
14272        // Different dialects have different boolean literal formats
14273        match self.config.dialect {
14274            // SQL Server typically uses 1/0 for boolean literals in many contexts
14275            // However, TRUE/FALSE also works in modern versions
14276            Some(DialectType::TSQL) => {
14277                self.write(if b.value { "1" } else { "0" });
14278            }
14279            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14280            Some(DialectType::Oracle) => {
14281                self.write(if b.value { "1" } else { "0" });
14282            }
14283            // MySQL accepts TRUE/FALSE as aliases for 1/0
14284            Some(DialectType::MySQL) => {
14285                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14286            }
14287            // Most other dialects support TRUE/FALSE
14288            _ => {
14289                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14290            }
14291        }
14292        Ok(())
14293    }
14294
14295    /// Generate an identifier that's used as an alias name
14296    /// This quotes reserved keywords in addition to already-quoted identifiers
14297    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14298        let name = &id.name;
14299        let quote_style = &self.config.identifier_quote_style;
14300
14301        // For aliases, quote if:
14302        // 1. The identifier was explicitly quoted in the source
14303        // 2. The identifier is a reserved keyword for the current dialect
14304        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14305
14306        // Normalize identifier if configured
14307        let output_name = if self.config.normalize_identifiers && !id.quoted {
14308            name.to_lowercase()
14309        } else {
14310            name.to_string()
14311        };
14312
14313        if needs_quoting {
14314            // Escape any quote characters within the identifier
14315            let escaped_name = if quote_style.start == quote_style.end {
14316                output_name.replace(
14317                    quote_style.end,
14318                    &format!("{}{}", quote_style.end, quote_style.end),
14319                )
14320            } else {
14321                output_name.replace(
14322                    quote_style.end,
14323                    &format!("{}{}", quote_style.end, quote_style.end),
14324                )
14325            };
14326            self.write(&format!(
14327                "{}{}{}",
14328                quote_style.start, escaped_name, quote_style.end
14329            ));
14330        } else {
14331            self.write(&output_name);
14332        }
14333
14334        // Output trailing comments
14335        for comment in &id.trailing_comments {
14336            self.write(" ");
14337            self.write_formatted_comment(comment);
14338        }
14339        Ok(())
14340    }
14341
14342    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14343        use crate::dialects::DialectType;
14344
14345        let name = &id.name;
14346
14347        // For Athena, use backticks in Hive context, double quotes in Trino context
14348        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14349            && self.athena_hive_context
14350        {
14351            &IdentifierQuoteStyle::BACKTICK
14352        } else {
14353            &self.config.identifier_quote_style
14354        };
14355
14356        // Quote if:
14357        // 1. The identifier was explicitly quoted in the source
14358        // 2. The identifier is a reserved keyword for the current dialect
14359        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14360        // This matches Python sqlglot's identifier_sql behavior
14361        // Also quote identifiers starting with digits if the target dialect doesn't support them
14362        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14363        let needs_digit_quoting = starts_with_digit
14364            && !self.config.identifiers_can_start_with_digit
14365            && self.config.dialect.is_some();
14366        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14367            && name.len() > 2
14368            && (name.starts_with("0x") || name.starts_with("0X"))
14369            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14370        let needs_quoting = id.quoted
14371            || self.is_reserved_keyword(name)
14372            || self.config.always_quote_identifiers
14373            || needs_digit_quoting
14374            || mysql_invalid_hex_identifier;
14375
14376        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14377        // When quoted, we need to output `name`(16) not `name(16)`
14378        let (base_name, suffix) = if needs_quoting {
14379            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14380            if let Some(paren_pos) = name.find('(') {
14381                let base = &name[..paren_pos];
14382                let rest = &name[paren_pos..];
14383                // Verify it looks like (digits) or (digits) ASC/DESC
14384                if rest.starts_with('(')
14385                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14386                {
14387                    // Check if content between parens is all digits
14388                    let close_paren = rest.find(')').unwrap_or(rest.len());
14389                    let inside = &rest[1..close_paren];
14390                    if inside.chars().all(|c| c.is_ascii_digit()) {
14391                        (base.to_string(), rest.to_string())
14392                    } else {
14393                        (name.to_string(), String::new())
14394                    }
14395                } else {
14396                    (name.to_string(), String::new())
14397                }
14398            } else if name.ends_with(" ASC") {
14399                let base = &name[..name.len() - 4];
14400                (base.to_string(), " ASC".to_string())
14401            } else if name.ends_with(" DESC") {
14402                let base = &name[..name.len() - 5];
14403                (base.to_string(), " DESC".to_string())
14404            } else {
14405                (name.to_string(), String::new())
14406            }
14407        } else {
14408            (name.to_string(), String::new())
14409        };
14410
14411        // Normalize identifier if configured, with special handling for Exasol
14412        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14413        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14414        let output_name = if self.config.normalize_identifiers && !id.quoted {
14415            base_name.to_lowercase()
14416        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14417            && !id.quoted
14418            && self.is_reserved_keyword(name)
14419        {
14420            // Exasol: uppercase reserved keywords when quoting them
14421            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14422            base_name.to_uppercase()
14423        } else {
14424            base_name
14425        };
14426
14427        if needs_quoting {
14428            // Escape any quote characters within the identifier
14429            let escaped_name = if quote_style.start == quote_style.end {
14430                // Same start/end char (e.g., " or `) - double the quote char
14431                output_name.replace(
14432                    quote_style.end,
14433                    &format!("{}{}", quote_style.end, quote_style.end),
14434                )
14435            } else {
14436                // Different start/end (e.g., [ and ]) - escape only the end char
14437                output_name.replace(
14438                    quote_style.end,
14439                    &format!("{}{}", quote_style.end, quote_style.end),
14440                )
14441            };
14442            self.write(&format!(
14443                "{}{}{}{}",
14444                quote_style.start, escaped_name, quote_style.end, suffix
14445            ));
14446        } else {
14447            self.write(&output_name);
14448        }
14449
14450        // Output trailing comments
14451        for comment in &id.trailing_comments {
14452            self.write(" ");
14453            self.write_formatted_comment(comment);
14454        }
14455        Ok(())
14456    }
14457
14458    fn generate_column(&mut self, col: &Column) -> Result<()> {
14459        use crate::dialects::DialectType;
14460
14461        if let Some(table) = &col.table {
14462            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14463            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14464            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14465            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14466                && !table.quoted
14467                && table.name.eq_ignore_ascii_case("LOCAL");
14468
14469            if is_exasol_local_prefix {
14470                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14471                self.write("LOCAL");
14472            } else {
14473                self.generate_identifier(table)?;
14474            }
14475            self.write(".");
14476        }
14477        self.generate_identifier(&col.name)?;
14478        // Oracle-style join marker (+)
14479        // Only output if dialect supports it (Oracle, Exasol)
14480        if col.join_mark && self.config.supports_column_join_marks {
14481            self.write(" (+)");
14482        }
14483        // Output trailing comments
14484        for comment in &col.trailing_comments {
14485            self.write_space();
14486            self.write_formatted_comment(comment);
14487        }
14488        Ok(())
14489    }
14490
14491    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14492    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14493    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14494        use crate::dialects::DialectType;
14495        use crate::expressions::PseudocolumnType;
14496
14497        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14498        if pc.kind == PseudocolumnType::Sysdate
14499            && !matches!(
14500                self.config.dialect,
14501                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14502            )
14503        {
14504            self.write_keyword("CURRENT_TIMESTAMP");
14505            // Add () for dialects that expect it
14506            if matches!(
14507                self.config.dialect,
14508                Some(DialectType::MySQL)
14509                    | Some(DialectType::ClickHouse)
14510                    | Some(DialectType::Spark)
14511                    | Some(DialectType::Databricks)
14512                    | Some(DialectType::Hive)
14513            ) {
14514                self.write("()");
14515            }
14516        } else {
14517            self.write(pc.kind.as_str());
14518        }
14519        Ok(())
14520    }
14521
14522    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14523    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14524        use crate::dialects::DialectType;
14525
14526        // Generate native CONNECT BY for Oracle and Snowflake
14527        // For other dialects, add a comment noting manual conversion needed
14528        let supports_connect_by = matches!(
14529            self.config.dialect,
14530            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14531        );
14532
14533        if !supports_connect_by && self.config.dialect.is_some() {
14534            // Add comment for unsupported dialects
14535            if self.config.pretty {
14536                self.write_newline();
14537            } else {
14538                self.write_space();
14539            }
14540            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14541        }
14542
14543        // Generate START WITH if present (before CONNECT BY)
14544        if let Some(start) = &connect.start {
14545            if self.config.pretty {
14546                self.write_newline();
14547            } else {
14548                self.write_space();
14549            }
14550            self.write_keyword("START WITH");
14551            self.write_space();
14552            self.generate_expression(start)?;
14553        }
14554
14555        // Generate CONNECT BY
14556        if self.config.pretty {
14557            self.write_newline();
14558        } else {
14559            self.write_space();
14560        }
14561        self.write_keyword("CONNECT BY");
14562        if connect.nocycle {
14563            self.write_space();
14564            self.write_keyword("NOCYCLE");
14565        }
14566        self.write_space();
14567        self.generate_expression(&connect.connect)?;
14568
14569        Ok(())
14570    }
14571
14572    /// Generate Connect expression (for Expression::Connect variant)
14573    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14574        self.generate_connect(connect)
14575    }
14576
14577    /// Generate PRIOR expression
14578    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14579        self.write_keyword("PRIOR");
14580        self.write_space();
14581        self.generate_expression(&prior.this)?;
14582        Ok(())
14583    }
14584
14585    /// Generate CONNECT_BY_ROOT function
14586    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14587    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14588        self.write_keyword("CONNECT_BY_ROOT");
14589        self.write_space();
14590        self.generate_expression(&cbr.this)?;
14591        Ok(())
14592    }
14593
14594    /// Generate MATCH_RECOGNIZE clause
14595    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14596        use crate::dialects::DialectType;
14597
14598        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14599        let supports_match_recognize = matches!(
14600            self.config.dialect,
14601            Some(DialectType::Oracle)
14602                | Some(DialectType::Snowflake)
14603                | Some(DialectType::Presto)
14604                | Some(DialectType::Trino)
14605        );
14606
14607        // Generate the source table first
14608        if let Some(source) = &mr.this {
14609            self.generate_expression(source)?;
14610        }
14611
14612        if !supports_match_recognize {
14613            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14614            return Ok(());
14615        }
14616
14617        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14618        if self.config.pretty {
14619            self.write_newline();
14620        } else {
14621            self.write_space();
14622        }
14623
14624        self.write_keyword("MATCH_RECOGNIZE");
14625        self.write(" (");
14626
14627        if self.config.pretty {
14628            self.indent_level += 1;
14629        }
14630
14631        let mut needs_separator = false;
14632
14633        // PARTITION BY
14634        if let Some(partition_by) = &mr.partition_by {
14635            if !partition_by.is_empty() {
14636                if self.config.pretty {
14637                    self.write_newline();
14638                    self.write_indent();
14639                }
14640                self.write_keyword("PARTITION BY");
14641                self.write_space();
14642                for (i, expr) in partition_by.iter().enumerate() {
14643                    if i > 0 {
14644                        self.write(", ");
14645                    }
14646                    self.generate_expression(expr)?;
14647                }
14648                needs_separator = true;
14649            }
14650        }
14651
14652        // ORDER BY
14653        if let Some(order_by) = &mr.order_by {
14654            if !order_by.is_empty() {
14655                if needs_separator {
14656                    if self.config.pretty {
14657                        self.write_newline();
14658                        self.write_indent();
14659                    } else {
14660                        self.write_space();
14661                    }
14662                } else if self.config.pretty {
14663                    self.write_newline();
14664                    self.write_indent();
14665                }
14666                self.write_keyword("ORDER BY");
14667                // In pretty mode, put each ORDER BY column on a new indented line
14668                if self.config.pretty {
14669                    self.indent_level += 1;
14670                    for (i, ordered) in order_by.iter().enumerate() {
14671                        if i > 0 {
14672                            self.write(",");
14673                        }
14674                        self.write_newline();
14675                        self.write_indent();
14676                        self.generate_ordered(ordered)?;
14677                    }
14678                    self.indent_level -= 1;
14679                } else {
14680                    self.write_space();
14681                    for (i, ordered) in order_by.iter().enumerate() {
14682                        if i > 0 {
14683                            self.write(", ");
14684                        }
14685                        self.generate_ordered(ordered)?;
14686                    }
14687                }
14688                needs_separator = true;
14689            }
14690        }
14691
14692        // MEASURES
14693        if let Some(measures) = &mr.measures {
14694            if !measures.is_empty() {
14695                if needs_separator {
14696                    if self.config.pretty {
14697                        self.write_newline();
14698                        self.write_indent();
14699                    } else {
14700                        self.write_space();
14701                    }
14702                } else if self.config.pretty {
14703                    self.write_newline();
14704                    self.write_indent();
14705                }
14706                self.write_keyword("MEASURES");
14707                // In pretty mode, put each MEASURE on a new indented line
14708                if self.config.pretty {
14709                    self.indent_level += 1;
14710                    for (i, measure) in measures.iter().enumerate() {
14711                        if i > 0 {
14712                            self.write(",");
14713                        }
14714                        self.write_newline();
14715                        self.write_indent();
14716                        // Handle RUNNING/FINAL prefix
14717                        if let Some(semantics) = &measure.window_frame {
14718                            match semantics {
14719                                MatchRecognizeSemantics::Running => {
14720                                    self.write_keyword("RUNNING");
14721                                    self.write_space();
14722                                }
14723                                MatchRecognizeSemantics::Final => {
14724                                    self.write_keyword("FINAL");
14725                                    self.write_space();
14726                                }
14727                            }
14728                        }
14729                        self.generate_expression(&measure.this)?;
14730                    }
14731                    self.indent_level -= 1;
14732                } else {
14733                    self.write_space();
14734                    for (i, measure) in measures.iter().enumerate() {
14735                        if i > 0 {
14736                            self.write(", ");
14737                        }
14738                        // Handle RUNNING/FINAL prefix
14739                        if let Some(semantics) = &measure.window_frame {
14740                            match semantics {
14741                                MatchRecognizeSemantics::Running => {
14742                                    self.write_keyword("RUNNING");
14743                                    self.write_space();
14744                                }
14745                                MatchRecognizeSemantics::Final => {
14746                                    self.write_keyword("FINAL");
14747                                    self.write_space();
14748                                }
14749                            }
14750                        }
14751                        self.generate_expression(&measure.this)?;
14752                    }
14753                }
14754                needs_separator = true;
14755            }
14756        }
14757
14758        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14759        if let Some(rows) = &mr.rows {
14760            if needs_separator {
14761                if self.config.pretty {
14762                    self.write_newline();
14763                    self.write_indent();
14764                } else {
14765                    self.write_space();
14766                }
14767            } else if self.config.pretty {
14768                self.write_newline();
14769                self.write_indent();
14770            }
14771            match rows {
14772                MatchRecognizeRows::OneRowPerMatch => {
14773                    self.write_keyword("ONE ROW PER MATCH");
14774                }
14775                MatchRecognizeRows::AllRowsPerMatch => {
14776                    self.write_keyword("ALL ROWS PER MATCH");
14777                }
14778                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14779                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14780                }
14781                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14782                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14783                }
14784                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14785                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14786                }
14787            }
14788            needs_separator = true;
14789        }
14790
14791        // AFTER MATCH SKIP
14792        if let Some(after) = &mr.after {
14793            if needs_separator {
14794                if self.config.pretty {
14795                    self.write_newline();
14796                    self.write_indent();
14797                } else {
14798                    self.write_space();
14799                }
14800            } else if self.config.pretty {
14801                self.write_newline();
14802                self.write_indent();
14803            }
14804            match after {
14805                MatchRecognizeAfter::PastLastRow => {
14806                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14807                }
14808                MatchRecognizeAfter::ToNextRow => {
14809                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14810                }
14811                MatchRecognizeAfter::ToFirst(ident) => {
14812                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14813                    self.write_space();
14814                    self.generate_identifier(ident)?;
14815                }
14816                MatchRecognizeAfter::ToLast(ident) => {
14817                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14818                    self.write_space();
14819                    self.generate_identifier(ident)?;
14820                }
14821            }
14822            needs_separator = true;
14823        }
14824
14825        // PATTERN
14826        if let Some(pattern) = &mr.pattern {
14827            if needs_separator {
14828                if self.config.pretty {
14829                    self.write_newline();
14830                    self.write_indent();
14831                } else {
14832                    self.write_space();
14833                }
14834            } else if self.config.pretty {
14835                self.write_newline();
14836                self.write_indent();
14837            }
14838            self.write_keyword("PATTERN");
14839            self.write_space();
14840            self.write("(");
14841            self.write(pattern);
14842            self.write(")");
14843            needs_separator = true;
14844        }
14845
14846        // DEFINE
14847        if let Some(define) = &mr.define {
14848            if !define.is_empty() {
14849                if needs_separator {
14850                    if self.config.pretty {
14851                        self.write_newline();
14852                        self.write_indent();
14853                    } else {
14854                        self.write_space();
14855                    }
14856                } else if self.config.pretty {
14857                    self.write_newline();
14858                    self.write_indent();
14859                }
14860                self.write_keyword("DEFINE");
14861                // In pretty mode, put each DEFINE on a new indented line
14862                if self.config.pretty {
14863                    self.indent_level += 1;
14864                    for (i, (name, expr)) in define.iter().enumerate() {
14865                        if i > 0 {
14866                            self.write(",");
14867                        }
14868                        self.write_newline();
14869                        self.write_indent();
14870                        self.generate_identifier(name)?;
14871                        self.write(" AS ");
14872                        self.generate_expression(expr)?;
14873                    }
14874                    self.indent_level -= 1;
14875                } else {
14876                    self.write_space();
14877                    for (i, (name, expr)) in define.iter().enumerate() {
14878                        if i > 0 {
14879                            self.write(", ");
14880                        }
14881                        self.generate_identifier(name)?;
14882                        self.write(" AS ");
14883                        self.generate_expression(expr)?;
14884                    }
14885                }
14886            }
14887        }
14888
14889        if self.config.pretty {
14890            self.indent_level -= 1;
14891            self.write_newline();
14892        }
14893        self.write(")");
14894
14895        // Alias - only include AS if it was explicitly present in the input
14896        if let Some(alias) = &mr.alias {
14897            self.write(" ");
14898            if mr.alias_explicit_as {
14899                self.write_keyword("AS");
14900                self.write(" ");
14901            }
14902            self.generate_identifier(alias)?;
14903        }
14904
14905        Ok(())
14906    }
14907
14908    /// Generate a query hint /*+ ... */
14909    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14910        use crate::dialects::DialectType;
14911
14912        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14913        let supports_hints = matches!(
14914            self.config.dialect,
14915            None |  // No dialect = preserve everything
14916            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14917            Some(DialectType::Spark) | Some(DialectType::Hive) |
14918            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14919        );
14920
14921        if !supports_hints || hint.expressions.is_empty() {
14922            return Ok(());
14923        }
14924
14925        // First, expand raw hint text into individual hint strings
14926        // This handles the case where the parser stored multiple hints as a single raw string
14927        let mut hint_strings: Vec<String> = Vec::new();
14928        for expr in &hint.expressions {
14929            match expr {
14930                HintExpression::Raw(text) => {
14931                    // Parse raw hint text into individual hint function calls
14932                    let parsed = self.parse_raw_hint_text(text);
14933                    hint_strings.extend(parsed);
14934                }
14935                _ => {
14936                    hint_strings.push(self.hint_expression_to_string(expr)?);
14937                }
14938            }
14939        }
14940
14941        // In pretty mode with multiple hints, always use multiline format
14942        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14943        // always joins with newlines in pretty mode
14944        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14945
14946        if use_multiline {
14947            // Pretty print with each hint on its own line
14948            self.write(" /*+ ");
14949            for (i, hint_str) in hint_strings.iter().enumerate() {
14950                if i > 0 {
14951                    self.write_newline();
14952                    self.write("  "); // 2-space indent within hint block
14953                }
14954                self.write(hint_str);
14955            }
14956            self.write(" */");
14957        } else {
14958            // Single line format
14959            self.write(" /*+ ");
14960            let sep = match self.config.dialect {
14961                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14962                _ => " ",
14963            };
14964            for (i, hint_str) in hint_strings.iter().enumerate() {
14965                if i > 0 {
14966                    self.write(sep);
14967                }
14968                self.write(hint_str);
14969            }
14970            self.write(" */");
14971        }
14972
14973        Ok(())
14974    }
14975
14976    /// Parse raw hint text into individual hint function calls
14977    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14978    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14979    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14980        let mut results = Vec::new();
14981        let mut chars = text.chars().peekable();
14982        let mut current = String::new();
14983        let mut paren_depth = 0;
14984        let mut has_unparseable_content = false;
14985        let mut position_after_last_function = 0;
14986        let mut char_position = 0;
14987
14988        while let Some(c) = chars.next() {
14989            char_position += c.len_utf8();
14990            match c {
14991                '(' => {
14992                    paren_depth += 1;
14993                    current.push(c);
14994                }
14995                ')' => {
14996                    paren_depth -= 1;
14997                    current.push(c);
14998                    // When we close the outer parenthesis, we've completed a hint function
14999                    if paren_depth == 0 {
15000                        let trimmed = current.trim().to_string();
15001                        if !trimmed.is_empty() {
15002                            // Format this hint for pretty printing if needed
15003                            let formatted = self.format_hint_function(&trimmed);
15004                            results.push(formatted);
15005                        }
15006                        current.clear();
15007                        position_after_last_function = char_position;
15008                    }
15009                }
15010                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15011                    // Space/comma/whitespace outside parentheses - skip
15012                }
15013                _ if paren_depth == 0 => {
15014                    // Character outside parentheses - accumulate for potential hint name
15015                    current.push(c);
15016                }
15017                _ => {
15018                    current.push(c);
15019                }
15020            }
15021        }
15022
15023        // Check if there's remaining text after the last function call
15024        let remaining_text = text[position_after_last_function..].trim();
15025        if !remaining_text.is_empty() {
15026            // Check if it looks like valid hint function names
15027            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15028            // If we see multiple words without parens, it's likely unparseable
15029            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15030            let looks_like_hint_functions = words.iter().all(|word| {
15031                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15032                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15033            });
15034
15035            if !looks_like_hint_functions && words.len() > 1 {
15036                has_unparseable_content = true;
15037            }
15038        }
15039
15040        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15041        if has_unparseable_content {
15042            return vec![text.trim().to_string()];
15043        }
15044
15045        // If we couldn't parse anything, return the original text as a single hint
15046        if results.is_empty() {
15047            results.push(text.trim().to_string());
15048        }
15049
15050        results
15051    }
15052
15053    /// Format a hint function for pretty printing
15054    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15055    fn format_hint_function(&self, hint: &str) -> String {
15056        if !self.config.pretty {
15057            return hint.to_string();
15058        }
15059
15060        // Try to parse NAME(args) pattern
15061        if let Some(paren_pos) = hint.find('(') {
15062            if hint.ends_with(')') {
15063                let name = &hint[..paren_pos];
15064                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15065
15066                // Parse arguments (space-separated for Oracle hints)
15067                let args: Vec<&str> = args_str.split_whitespace().collect();
15068
15069                // Calculate total width of arguments
15070                let total_args_width: usize =
15071                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15072
15073                // If too wide, format on multiple lines
15074                if total_args_width > self.config.max_text_width && !args.is_empty() {
15075                    let mut result = format!("{}(\n", name);
15076                    for arg in &args {
15077                        result.push_str("    "); // 4-space indent for args
15078                        result.push_str(arg);
15079                        result.push('\n');
15080                    }
15081                    result.push_str("  )"); // 2-space indent for closing paren
15082                    return result;
15083                }
15084            }
15085        }
15086
15087        hint.to_string()
15088    }
15089
15090    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15091    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15092        match expr {
15093            HintExpression::Function { name, args } => {
15094                // Generate each argument to a string
15095                let arg_strings: Vec<String> = args
15096                    .iter()
15097                    .map(|arg| {
15098                        let mut gen = Generator::with_config(self.config.clone());
15099                        gen.generate_expression(arg)?;
15100                        Ok(gen.output)
15101                    })
15102                    .collect::<Result<Vec<_>>>()?;
15103
15104                // Oracle hints use space-separated arguments, not comma-separated
15105                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15106                    + arg_strings.len().saturating_sub(1); // spaces between args
15107
15108                // Check if function args need multiline formatting
15109                // Use too_wide check for argument formatting
15110                let args_multiline =
15111                    self.config.pretty && total_args_width > self.config.max_text_width;
15112
15113                if args_multiline && !arg_strings.is_empty() {
15114                    // Multiline format for long argument lists
15115                    let mut result = format!("{}(\n", name);
15116                    for arg_str in &arg_strings {
15117                        result.push_str("    "); // 4-space indent for args
15118                        result.push_str(arg_str);
15119                        result.push('\n');
15120                    }
15121                    result.push_str("  )"); // 2-space indent for closing paren
15122                    Ok(result)
15123                } else {
15124                    // Single line format with space-separated args (Oracle style)
15125                    let args_str = arg_strings.join(" ");
15126                    Ok(format!("{}({})", name, args_str))
15127                }
15128            }
15129            HintExpression::Identifier(name) => Ok(name.clone()),
15130            HintExpression::Raw(text) => {
15131                // For pretty printing, try to format the raw text
15132                if self.config.pretty {
15133                    Ok(self.format_hint_function(text))
15134                } else {
15135                    Ok(text.clone())
15136                }
15137            }
15138        }
15139    }
15140
15141    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15142        // PostgreSQL ONLY modifier: prevents scanning child tables
15143        if table.only {
15144            self.write_keyword("ONLY");
15145            self.write_space();
15146        }
15147
15148        // Check for Snowflake IDENTIFIER() function
15149        if let Some(ref identifier_func) = table.identifier_func {
15150            self.generate_expression(identifier_func)?;
15151        } else {
15152            if let Some(catalog) = &table.catalog {
15153                self.generate_identifier(catalog)?;
15154                self.write(".");
15155            }
15156            if let Some(schema) = &table.schema {
15157                self.generate_identifier(schema)?;
15158                self.write(".");
15159            }
15160            self.generate_identifier(&table.name)?;
15161        }
15162
15163        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15164        if let Some(changes) = &table.changes {
15165            self.write(" ");
15166            self.generate_changes(changes)?;
15167        }
15168
15169        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15170        if !table.partitions.is_empty() {
15171            self.write_space();
15172            self.write_keyword("PARTITION");
15173            self.write("(");
15174            for (i, partition) in table.partitions.iter().enumerate() {
15175                if i > 0 {
15176                    self.write(", ");
15177                }
15178                self.generate_identifier(partition)?;
15179            }
15180            self.write(")");
15181        }
15182
15183        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15184        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15185        if table.changes.is_none() {
15186            if let Some(when) = &table.when {
15187                self.write_space();
15188                self.generate_historical_data(when)?;
15189            }
15190        }
15191
15192        // Output TSQL FOR SYSTEM_TIME temporal clause
15193        if let Some(ref system_time) = table.system_time {
15194            self.write_space();
15195            self.write(system_time);
15196        }
15197
15198        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15199        if let Some(ref version) = table.version {
15200            self.write_space();
15201            self.generate_version(version)?;
15202        }
15203
15204        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15205        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15206        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15207        let alias_post_tablesample = self.config.alias_post_tablesample;
15208
15209        if alias_post_tablesample {
15210            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15211            self.generate_table_sample_clause(table)?;
15212        }
15213
15214        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15215        // For SQLite, INDEXED BY hints come after the alias, so skip here
15216        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15217            && table.hints.iter().any(|h| {
15218                if let Expression::Identifier(id) = h {
15219                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15220                } else {
15221                    false
15222                }
15223            });
15224        if !table.hints.is_empty() && !is_sqlite_hint {
15225            for hint in &table.hints {
15226                self.write_space();
15227                self.generate_expression(hint)?;
15228            }
15229        }
15230
15231        if let Some(alias) = &table.alias {
15232            self.write_space();
15233            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15234            // Generic mode and most dialects always use AS for table aliases
15235            let always_use_as = self.config.dialect.is_none()
15236                || matches!(
15237                    self.config.dialect,
15238                    Some(DialectType::Generic)
15239                        | Some(DialectType::PostgreSQL)
15240                        | Some(DialectType::Redshift)
15241                        | Some(DialectType::Snowflake)
15242                        | Some(DialectType::BigQuery)
15243                        | Some(DialectType::Presto)
15244                        | Some(DialectType::Trino)
15245                        | Some(DialectType::TSQL)
15246                        | Some(DialectType::Fabric)
15247                        | Some(DialectType::MySQL)
15248                        | Some(DialectType::Spark)
15249                        | Some(DialectType::Hive)
15250                        | Some(DialectType::SQLite)
15251                        | Some(DialectType::Drill)
15252                );
15253            let is_stage_ref = table.name.name.starts_with('@');
15254            // Oracle never uses AS for table aliases
15255            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15256            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15257                self.write_keyword("AS");
15258                self.write_space();
15259            }
15260            self.generate_identifier(alias)?;
15261
15262            // Output column aliases if present: AS t(c1, c2)
15263            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15264            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15265                self.write("(");
15266                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15267                    if i > 0 {
15268                        self.write(", ");
15269                    }
15270                    self.generate_identifier(col_alias)?;
15271                }
15272                self.write(")");
15273            }
15274        }
15275
15276        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15277        if !alias_post_tablesample {
15278            self.generate_table_sample_clause(table)?;
15279        }
15280
15281        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15282        if is_sqlite_hint {
15283            for hint in &table.hints {
15284                self.write_space();
15285                self.generate_expression(hint)?;
15286            }
15287        }
15288
15289        // ClickHouse FINAL modifier
15290        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15291            self.write_space();
15292            self.write_keyword("FINAL");
15293        }
15294
15295        // Output trailing comments
15296        for comment in &table.trailing_comments {
15297            self.write_space();
15298            self.write_formatted_comment(comment);
15299        }
15300
15301        Ok(())
15302    }
15303
15304    /// Helper to output TABLESAMPLE clause for a table reference
15305    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15306        if let Some(ref ts) = table.table_sample {
15307            self.write_space();
15308            if ts.is_using_sample {
15309                self.write_keyword("USING SAMPLE");
15310            } else {
15311                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15312                self.write_keyword(self.config.tablesample_keywords);
15313            }
15314            self.generate_sample_body(ts)?;
15315            // Seed for table-level sample - use dialect's configured keyword
15316            if let Some(ref seed) = ts.seed {
15317                self.write_space();
15318                self.write_keyword(self.config.tablesample_seed_keyword);
15319                self.write(" (");
15320                self.generate_expression(seed)?;
15321                self.write(")");
15322            }
15323        }
15324        Ok(())
15325    }
15326
15327    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15328        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15329        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15330
15331        if sr.quoted {
15332            self.write("'");
15333        }
15334
15335        self.write(&sr.name);
15336        if let Some(path) = &sr.path {
15337            self.write(path);
15338        }
15339
15340        if sr.quoted {
15341            self.write("'");
15342        }
15343
15344        // Output FILE_FORMAT and PATTERN if present
15345        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15346        if has_options {
15347            self.write(" (");
15348            let mut first = true;
15349
15350            if let Some(file_format) = &sr.file_format {
15351                if !first {
15352                    self.write(", ");
15353                }
15354                self.write_keyword("FILE_FORMAT");
15355                self.write(" => ");
15356                self.generate_expression(file_format)?;
15357                first = false;
15358            }
15359
15360            if let Some(pattern) = &sr.pattern {
15361                if !first {
15362                    self.write(", ");
15363                }
15364                self.write_keyword("PATTERN");
15365                self.write(" => '");
15366                self.write(pattern);
15367                self.write("'");
15368            }
15369
15370            self.write(")");
15371        }
15372        Ok(())
15373    }
15374
15375    fn generate_star(&mut self, star: &Star) -> Result<()> {
15376        use crate::dialects::DialectType;
15377
15378        if let Some(table) = &star.table {
15379            self.generate_identifier(table)?;
15380            self.write(".");
15381        }
15382        self.write("*");
15383
15384        // Generate EXCLUDE/EXCEPT clause based on dialect
15385        if let Some(except) = &star.except {
15386            if !except.is_empty() {
15387                self.write_space();
15388                // Use dialect-appropriate keyword
15389                match self.config.dialect {
15390                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15391                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15392                        self.write_keyword("EXCLUDE")
15393                    }
15394                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15395                }
15396                self.write(" (");
15397                for (i, col) in except.iter().enumerate() {
15398                    if i > 0 {
15399                        self.write(", ");
15400                    }
15401                    self.generate_identifier(col)?;
15402                }
15403                self.write(")");
15404            }
15405        }
15406
15407        // Generate REPLACE clause
15408        if let Some(replace) = &star.replace {
15409            if !replace.is_empty() {
15410                self.write_space();
15411                self.write_keyword("REPLACE");
15412                self.write(" (");
15413                for (i, alias) in replace.iter().enumerate() {
15414                    if i > 0 {
15415                        self.write(", ");
15416                    }
15417                    self.generate_expression(&alias.this)?;
15418                    self.write_space();
15419                    self.write_keyword("AS");
15420                    self.write_space();
15421                    self.generate_identifier(&alias.alias)?;
15422                }
15423                self.write(")");
15424            }
15425        }
15426
15427        // Generate RENAME clause (Snowflake specific)
15428        if let Some(rename) = &star.rename {
15429            if !rename.is_empty() {
15430                self.write_space();
15431                self.write_keyword("RENAME");
15432                self.write(" (");
15433                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15434                    if i > 0 {
15435                        self.write(", ");
15436                    }
15437                    self.generate_identifier(old_name)?;
15438                    self.write_space();
15439                    self.write_keyword("AS");
15440                    self.write_space();
15441                    self.generate_identifier(new_name)?;
15442                }
15443                self.write(")");
15444            }
15445        }
15446
15447        // Output trailing comments
15448        for comment in &star.trailing_comments {
15449            self.write_space();
15450            self.write_formatted_comment(comment);
15451        }
15452
15453        Ok(())
15454    }
15455
15456    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15457    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15458        self.write("{");
15459        match expr {
15460            Expression::Star(star) => {
15461                // Generate the star (table.* or just * with optional EXCLUDE)
15462                self.generate_star(star)?;
15463            }
15464            Expression::ILike(ilike) => {
15465                // {* ILIKE 'pattern'} syntax
15466                self.generate_expression(&ilike.left)?;
15467                self.write_space();
15468                self.write_keyword("ILIKE");
15469                self.write_space();
15470                self.generate_expression(&ilike.right)?;
15471            }
15472            _ => {
15473                self.generate_expression(expr)?;
15474            }
15475        }
15476        self.write("}");
15477        Ok(())
15478    }
15479
15480    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15481        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15482        // to avoid duplication (comments are captured as both Column.trailing_comments
15483        // and Alias.pre_alias_comments during parsing)
15484        match &alias.this {
15485            Expression::Column(col) => {
15486                // Generate column without trailing comments - they're in pre_alias_comments
15487                if let Some(table) = &col.table {
15488                    self.generate_identifier(table)?;
15489                    self.write(".");
15490                }
15491                self.generate_identifier(&col.name)?;
15492            }
15493            _ => {
15494                self.generate_expression(&alias.this)?;
15495            }
15496        }
15497
15498        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15499        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15500        // keep pre-alias comments in their original position (between expression and AS).
15501        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15502            for comment in &alias.pre_alias_comments {
15503                self.write_space();
15504                self.write_formatted_comment(comment);
15505            }
15506        }
15507
15508        use crate::dialects::DialectType;
15509
15510        // Determine if we should skip AS keyword for table-valued function aliases
15511        // Oracle and some other dialects don't use AS for table aliases
15512        // Note: We specifically use TableFromRows here, NOT Function, because Function
15513        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15514        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15515        let is_table_source = matches!(
15516            &alias.this,
15517            Expression::JSONTable(_)
15518                | Expression::XMLTable(_)
15519                | Expression::TableFromRows(_)
15520                | Expression::Unnest(_)
15521                | Expression::MatchRecognize(_)
15522                | Expression::Select(_)
15523                | Expression::Subquery(_)
15524                | Expression::Paren(_)
15525        );
15526        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15527        let skip_as = is_table_source && dialect_skips_table_alias_as;
15528
15529        self.write_space();
15530        if !skip_as {
15531            self.write_keyword("AS");
15532            self.write_space();
15533        }
15534
15535        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15536        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15537
15538        // Check if we have column aliases only (no table alias name)
15539        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15540            // Generate AS (col1, col2, ...)
15541            self.write("(");
15542            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15543                if i > 0 {
15544                    self.write(", ");
15545                }
15546                self.generate_alias_identifier(col_alias)?;
15547            }
15548            self.write(")");
15549        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15550            // Generate AS alias(col1, col2, ...)
15551            self.generate_alias_identifier(&alias.alias)?;
15552            self.write("(");
15553            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15554                if i > 0 {
15555                    self.write(", ");
15556                }
15557                self.generate_alias_identifier(col_alias)?;
15558            }
15559            self.write(")");
15560        } else {
15561            // Simple alias (or BigQuery without column aliases)
15562            self.generate_alias_identifier(&alias.alias)?;
15563        }
15564
15565        // Output trailing comments (comments after the alias)
15566        for comment in &alias.trailing_comments {
15567            self.write_space();
15568            self.write_formatted_comment(comment);
15569        }
15570
15571        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15572        // moves pre-alias comments to after the alias. When there are trailing_comments,
15573        // the pre-alias comments were already lost (consumed as column trailing comments
15574        // that were then used as pre_alias_comments). We always emit them after alias.
15575        if alias.trailing_comments.is_empty() {
15576            for comment in &alias.pre_alias_comments {
15577                self.write_space();
15578                self.write_formatted_comment(comment);
15579            }
15580        }
15581
15582        Ok(())
15583    }
15584
15585    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15586        use crate::dialects::DialectType;
15587
15588        // SingleStore uses :> syntax
15589        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15590            self.generate_expression(&cast.this)?;
15591            self.write(" :> ");
15592            self.generate_data_type(&cast.to)?;
15593            return Ok(());
15594        }
15595
15596        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15597        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15598            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15599                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15600            if is_unknown_type {
15601                if let Some(format) = &cast.format {
15602                    self.write_keyword("CAST");
15603                    self.write("(");
15604                    self.generate_expression(&cast.this)?;
15605                    self.write_space();
15606                    self.write_keyword("AS");
15607                    self.write_space();
15608                    self.write_keyword("FORMAT");
15609                    self.write_space();
15610                    self.generate_expression(format)?;
15611                    self.write(")");
15612                    return Ok(());
15613                }
15614            }
15615        }
15616
15617        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15618        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15619        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15620            if let Some(format) = &cast.format {
15621                // Check if target type is DATE or TIMESTAMP
15622                let is_date = matches!(cast.to, DataType::Date);
15623                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15624
15625                if is_date || is_timestamp {
15626                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15627                    self.write_keyword(func_name);
15628                    self.write("(");
15629                    self.generate_expression(&cast.this)?;
15630                    self.write(", ");
15631
15632                    // Normalize format string for Oracle (HH -> HH12)
15633                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15634                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15635                        let normalized = self.normalize_oracle_format(fmt_str);
15636                        self.write("'");
15637                        self.write(&normalized);
15638                        self.write("'");
15639                    } else {
15640                        self.generate_expression(format)?;
15641                    }
15642
15643                    self.write(")");
15644                    return Ok(());
15645                }
15646            }
15647        }
15648
15649        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15650        // This preserves sqlglot's typed inline array literal output.
15651        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15652            if let Expression::Array(arr) = &cast.this {
15653                self.generate_data_type(&cast.to)?;
15654                // Output just the bracket content [values] without the ARRAY prefix
15655                self.write("[");
15656                for (i, expr) in arr.expressions.iter().enumerate() {
15657                    if i > 0 {
15658                        self.write(", ");
15659                    }
15660                    self.generate_expression(expr)?;
15661                }
15662                self.write("]");
15663                return Ok(());
15664            }
15665            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15666                self.generate_data_type(&cast.to)?;
15667                self.generate_expression(&cast.this)?;
15668                return Ok(());
15669            }
15670        }
15671
15672        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15673        // convert the inner Struct to ROW(values...) format
15674        if matches!(
15675            self.config.dialect,
15676            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15677        ) {
15678            if let Expression::Struct(ref s) = cast.this {
15679                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15680                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15681                    self.write_keyword("CAST");
15682                    self.write("(");
15683                    self.generate_struct_as_row(s)?;
15684                    self.write_space();
15685                    self.write_keyword("AS");
15686                    self.write_space();
15687                    self.generate_data_type(&cast.to)?;
15688                    self.write(")");
15689                    return Ok(());
15690                }
15691            }
15692        }
15693
15694        // Determine if we should use :: syntax based on dialect
15695        // PostgreSQL prefers :: for identity, most others prefer CAST()
15696        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15697
15698        if use_double_colon {
15699            // PostgreSQL :: syntax: expr::type
15700            self.generate_expression(&cast.this)?;
15701            self.write("::");
15702            self.generate_data_type(&cast.to)?;
15703        } else {
15704            // Standard CAST() syntax
15705            self.write_keyword("CAST");
15706            self.write("(");
15707            self.generate_expression(&cast.this)?;
15708            self.write_space();
15709            self.write_keyword("AS");
15710            self.write_space();
15711            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15712            // This matches Python sqlglot's CAST_MAPPING behavior
15713            if matches!(
15714                self.config.dialect,
15715                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15716            ) {
15717                match &cast.to {
15718                    DataType::Custom { ref name } => {
15719                        let upper = name.to_uppercase();
15720                        match upper.as_str() {
15721                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15722                            | "TINYBLOB" => {
15723                                self.write_keyword("CHAR");
15724                            }
15725                            _ => {
15726                                self.generate_data_type(&cast.to)?;
15727                            }
15728                        }
15729                    }
15730                    DataType::VarChar { length, .. } => {
15731                        // MySQL CAST: VARCHAR -> CHAR
15732                        self.write_keyword("CHAR");
15733                        if let Some(n) = length {
15734                            self.write(&format!("({})", n));
15735                        }
15736                    }
15737                    DataType::Text => {
15738                        // MySQL CAST: TEXT -> CHAR
15739                        self.write_keyword("CHAR");
15740                    }
15741                    DataType::Timestamp {
15742                        precision,
15743                        timezone: false,
15744                    } => {
15745                        // MySQL CAST: TIMESTAMP -> DATETIME
15746                        self.write_keyword("DATETIME");
15747                        if let Some(p) = precision {
15748                            self.write(&format!("({})", p));
15749                        }
15750                    }
15751                    _ => {
15752                        self.generate_data_type(&cast.to)?;
15753                    }
15754                }
15755            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15756                // Snowflake CAST: STRING -> VARCHAR
15757                match &cast.to {
15758                    DataType::String { length } => {
15759                        self.write_keyword("VARCHAR");
15760                        if let Some(n) = length {
15761                            self.write(&format!("({})", n));
15762                        }
15763                    }
15764                    _ => {
15765                        self.generate_data_type(&cast.to)?;
15766                    }
15767                }
15768            } else {
15769                self.generate_data_type(&cast.to)?;
15770            }
15771
15772            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15773            if let Some(default) = &cast.default {
15774                self.write_space();
15775                self.write_keyword("DEFAULT");
15776                self.write_space();
15777                self.generate_expression(default)?;
15778                self.write_space();
15779                self.write_keyword("ON");
15780                self.write_space();
15781                self.write_keyword("CONVERSION");
15782                self.write_space();
15783                self.write_keyword("ERROR");
15784            }
15785
15786            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15787            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15788            if let Some(format) = &cast.format {
15789                // Check if Oracle dialect - use comma syntax
15790                if matches!(
15791                    self.config.dialect,
15792                    Some(crate::dialects::DialectType::Oracle)
15793                ) {
15794                    self.write(", ");
15795                } else {
15796                    self.write_space();
15797                    self.write_keyword("FORMAT");
15798                    self.write_space();
15799                }
15800                self.generate_expression(format)?;
15801            }
15802
15803            self.write(")");
15804            // Output trailing comments
15805            for comment in &cast.trailing_comments {
15806                self.write_space();
15807                self.write_formatted_comment(comment);
15808            }
15809        }
15810        Ok(())
15811    }
15812
15813    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15814    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15815    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15816        self.write_keyword("ROW");
15817        self.write("(");
15818        for (i, (_, expr)) in s.fields.iter().enumerate() {
15819            if i > 0 {
15820                self.write(", ");
15821            }
15822            // Recursively convert inner Struct to ROW format
15823            if let Expression::Struct(ref inner_s) = expr {
15824                self.generate_struct_as_row(inner_s)?;
15825            } else {
15826                self.generate_expression(expr)?;
15827            }
15828        }
15829        self.write(")");
15830        Ok(())
15831    }
15832
15833    /// Normalize Oracle date/time format strings
15834    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15835    fn normalize_oracle_format(&self, format: &str) -> String {
15836        // Replace standalone HH with HH12 (but not HH12 or HH24)
15837        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15838        let mut result = String::new();
15839        let chars: Vec<char> = format.chars().collect();
15840        let mut i = 0;
15841
15842        while i < chars.len() {
15843            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15844                // Check what follows HH
15845                if i + 2 < chars.len() {
15846                    let next = chars[i + 2];
15847                    if next == '1' || next == '2' {
15848                        // This is HH12 or HH24, keep as is
15849                        result.push('H');
15850                        result.push('H');
15851                        i += 2;
15852                        continue;
15853                    }
15854                }
15855                // Standalone HH -> HH12
15856                result.push_str("HH12");
15857                i += 2;
15858            } else {
15859                result.push(chars[i]);
15860                i += 1;
15861            }
15862        }
15863
15864        result
15865    }
15866
15867    /// Check if the current dialect prefers :: cast syntax
15868    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15869    /// So we return false for all dialects to match Python sqlglot's behavior
15870    fn dialect_prefers_double_colon(&self) -> bool {
15871        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15872        // Even PostgreSQL outputs CAST() not ::
15873        false
15874    }
15875
15876    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15877    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15878        use crate::dialects::DialectType;
15879
15880        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15881        let use_percent_operator = matches!(
15882            self.config.dialect,
15883            Some(DialectType::Snowflake)
15884                | Some(DialectType::MySQL)
15885                | Some(DialectType::Presto)
15886                | Some(DialectType::Trino)
15887                | Some(DialectType::PostgreSQL)
15888                | Some(DialectType::DuckDB)
15889                | Some(DialectType::Hive)
15890                | Some(DialectType::Spark)
15891                | Some(DialectType::Databricks)
15892                | Some(DialectType::Athena)
15893        );
15894
15895        if use_percent_operator {
15896            // Wrap complex expressions in parens to preserve precedence
15897            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15898            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15899            if needs_paren(&f.this) {
15900                self.write("(");
15901                self.generate_expression(&f.this)?;
15902                self.write(")");
15903            } else {
15904                self.generate_expression(&f.this)?;
15905            }
15906            self.write(" % ");
15907            if needs_paren(&f.expression) {
15908                self.write("(");
15909                self.generate_expression(&f.expression)?;
15910                self.write(")");
15911            } else {
15912                self.generate_expression(&f.expression)?;
15913            }
15914            Ok(())
15915        } else {
15916            self.generate_binary_func("MOD", &f.this, &f.expression)
15917        }
15918    }
15919
15920    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15921    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15922        use crate::dialects::DialectType;
15923
15924        // Snowflake normalizes IFNULL to COALESCE
15925        let func_name = match self.config.dialect {
15926            Some(DialectType::Snowflake) => "COALESCE",
15927            _ => "IFNULL",
15928        };
15929
15930        self.generate_binary_func(func_name, &f.this, &f.expression)
15931    }
15932
15933    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15934    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15935        // Use original function name if preserved (for identity tests)
15936        if let Some(ref original_name) = f.original_name {
15937            return self.generate_binary_func(original_name, &f.this, &f.expression);
15938        }
15939
15940        // Otherwise, use dialect-specific function names
15941        use crate::dialects::DialectType;
15942        let func_name = match self.config.dialect {
15943            Some(DialectType::Snowflake)
15944            | Some(DialectType::ClickHouse)
15945            | Some(DialectType::PostgreSQL)
15946            | Some(DialectType::Presto)
15947            | Some(DialectType::Trino)
15948            | Some(DialectType::Athena)
15949            | Some(DialectType::DuckDB)
15950            | Some(DialectType::BigQuery)
15951            | Some(DialectType::Spark)
15952            | Some(DialectType::Databricks)
15953            | Some(DialectType::Hive) => "COALESCE",
15954            Some(DialectType::MySQL)
15955            | Some(DialectType::Doris)
15956            | Some(DialectType::StarRocks)
15957            | Some(DialectType::SingleStore)
15958            | Some(DialectType::TiDB) => "IFNULL",
15959            _ => "NVL",
15960        };
15961
15962        self.generate_binary_func(func_name, &f.this, &f.expression)
15963    }
15964
15965    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15966    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15967        use crate::dialects::DialectType;
15968
15969        // Snowflake normalizes STDDEV_SAMP to STDDEV
15970        let func_name = match self.config.dialect {
15971            Some(DialectType::Snowflake) => "STDDEV",
15972            _ => "STDDEV_SAMP",
15973        };
15974
15975        self.generate_agg_func(func_name, f)
15976    }
15977
15978    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15979        self.generate_expression(&coll.this)?;
15980        self.write_space();
15981        self.write_keyword("COLLATE");
15982        self.write_space();
15983        if coll.quoted {
15984            // Single-quoted string: COLLATE 'de_DE'
15985            self.write("'");
15986            self.write(&coll.collation);
15987            self.write("'");
15988        } else if coll.double_quoted {
15989            // Double-quoted identifier: COLLATE "de_DE"
15990            self.write("\"");
15991            self.write(&coll.collation);
15992            self.write("\"");
15993        } else {
15994            // Unquoted identifier: COLLATE de_DE
15995            self.write(&coll.collation);
15996        }
15997        Ok(())
15998    }
15999
16000    fn generate_case(&mut self, case: &Case) -> Result<()> {
16001        // In pretty mode, decide whether to expand based on total text width
16002        let multiline_case = if self.config.pretty {
16003            // Build the flat representation to check width
16004            let mut statements: Vec<String> = Vec::new();
16005            let operand_str = if let Some(operand) = &case.operand {
16006                let s = self.generate_to_string(operand)?;
16007                statements.push(format!("CASE {}", s));
16008                s
16009            } else {
16010                statements.push("CASE".to_string());
16011                String::new()
16012            };
16013            let _ = operand_str;
16014            for (condition, result) in &case.whens {
16015                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16016                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16017            }
16018            if let Some(else_) = &case.else_ {
16019                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16020            }
16021            statements.push("END".to_string());
16022            self.too_wide(&statements)
16023        } else {
16024            false
16025        };
16026
16027        self.write_keyword("CASE");
16028        if let Some(operand) = &case.operand {
16029            self.write_space();
16030            self.generate_expression(operand)?;
16031        }
16032        if multiline_case {
16033            self.indent_level += 1;
16034        }
16035        for (condition, result) in &case.whens {
16036            if multiline_case {
16037                self.write_newline();
16038                self.write_indent();
16039            } else {
16040                self.write_space();
16041            }
16042            self.write_keyword("WHEN");
16043            self.write_space();
16044            self.generate_expression(condition)?;
16045            if multiline_case {
16046                self.write_newline();
16047                self.write_indent();
16048            } else {
16049                self.write_space();
16050            }
16051            self.write_keyword("THEN");
16052            self.write_space();
16053            self.generate_expression(result)?;
16054        }
16055        if let Some(else_) = &case.else_ {
16056            if multiline_case {
16057                self.write_newline();
16058                self.write_indent();
16059            } else {
16060                self.write_space();
16061            }
16062            self.write_keyword("ELSE");
16063            self.write_space();
16064            self.generate_expression(else_)?;
16065        }
16066        if multiline_case {
16067            self.indent_level -= 1;
16068            self.write_newline();
16069            self.write_indent();
16070        } else {
16071            self.write_space();
16072        }
16073        self.write_keyword("END");
16074        // Emit any comments that were attached to the CASE keyword
16075        for comment in &case.comments {
16076            self.write(" ");
16077            self.write_formatted_comment(comment);
16078        }
16079        Ok(())
16080    }
16081
16082    fn generate_function(&mut self, func: &Function) -> Result<()> {
16083        // Normalize function name based on dialect settings
16084        let normalized_name = self.normalize_func_name(&func.name);
16085        let upper_name = func.name.to_uppercase();
16086
16087        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16088        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16089            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16090        {
16091            self.write("LIST_FILTER(");
16092            self.write("[");
16093            for (i, arg) in func.args.iter().enumerate() {
16094                if i > 0 {
16095                    self.write(", ");
16096                }
16097                self.generate_expression(arg)?;
16098            }
16099            self.write("], _u -> NOT _u IS NULL)");
16100            return Ok(());
16101        }
16102
16103        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16104        if upper_name == "STRUCT"
16105            && !matches!(
16106                self.config.dialect,
16107                Some(DialectType::BigQuery)
16108                    | Some(DialectType::Spark)
16109                    | Some(DialectType::Databricks)
16110                    | Some(DialectType::Hive)
16111                    | None
16112            )
16113        {
16114            return self.generate_struct_function_cross_dialect(func);
16115        }
16116
16117        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16118        // This is an internal marker function for ::? JSON path syntax
16119        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16120            self.generate_expression(&func.args[0])?;
16121            self.write("::?");
16122            // Extract the key from the string literal
16123            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16124                self.write(key);
16125            } else {
16126                self.generate_expression(&func.args[1])?;
16127            }
16128            return Ok(());
16129        }
16130
16131        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16132        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16133            self.generate_expression(&func.args[0])?;
16134            self.write(" # ");
16135            self.generate_expression(&func.args[1])?;
16136            return Ok(());
16137        }
16138
16139        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16140        if matches!(
16141            self.config.dialect,
16142            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16143        ) && upper_name == "TRY"
16144            && func.args.len() == 1
16145        {
16146            self.generate_expression(&func.args[0])?;
16147            return Ok(());
16148        }
16149
16150        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16151        if self.config.dialect == Some(DialectType::ClickHouse)
16152            && upper_name == "TOSTARTOFDAY"
16153            && func.args.len() == 1
16154        {
16155            self.write("dateTrunc('DAY', ");
16156            self.generate_expression(&func.args[0])?;
16157            self.write(")");
16158            return Ok(());
16159        }
16160
16161        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16162        if self.config.dialect == Some(DialectType::Redshift)
16163            && upper_name == "CONCAT"
16164            && func.args.len() >= 2
16165        {
16166            for (i, arg) in func.args.iter().enumerate() {
16167                if i > 0 {
16168                    self.write(" || ");
16169                }
16170                self.generate_expression(arg)?;
16171            }
16172            return Ok(());
16173        }
16174
16175        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16176        if self.config.dialect == Some(DialectType::Redshift)
16177            && upper_name == "CONCAT_WS"
16178            && func.args.len() >= 2
16179        {
16180            let sep = &func.args[0];
16181            for (i, arg) in func.args.iter().skip(1).enumerate() {
16182                if i > 0 {
16183                    self.write(" || ");
16184                    self.generate_expression(sep)?;
16185                    self.write(" || ");
16186                }
16187                self.generate_expression(arg)?;
16188            }
16189            return Ok(());
16190        }
16191
16192        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16193        // Unit should be unquoted uppercase identifier
16194        if self.config.dialect == Some(DialectType::Redshift)
16195            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16196            && func.args.len() == 3
16197        {
16198            self.write_keyword("DATEDIFF");
16199            self.write("(");
16200            // First arg is unit - normalize to unquoted uppercase
16201            self.write_redshift_date_part(&func.args[0]);
16202            self.write(", ");
16203            self.generate_expression(&func.args[1])?;
16204            self.write(", ");
16205            self.generate_expression(&func.args[2])?;
16206            self.write(")");
16207            return Ok(());
16208        }
16209
16210        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16211        // Unit should be unquoted uppercase identifier
16212        if self.config.dialect == Some(DialectType::Redshift)
16213            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16214            && func.args.len() == 3
16215        {
16216            self.write_keyword("DATEADD");
16217            self.write("(");
16218            // First arg is unit - normalize to unquoted uppercase
16219            self.write_redshift_date_part(&func.args[0]);
16220            self.write(", ");
16221            self.generate_expression(&func.args[1])?;
16222            self.write(", ");
16223            self.generate_expression(&func.args[2])?;
16224            self.write(")");
16225            return Ok(());
16226        }
16227
16228        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16229        if upper_name == "UUID_STRING"
16230            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16231        {
16232            let func_name = match self.config.dialect {
16233                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16234                Some(DialectType::BigQuery) => "GENERATE_UUID",
16235                _ => "UUID",
16236            };
16237            self.write_keyword(func_name);
16238            self.write("()");
16239            return Ok(());
16240        }
16241
16242        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16243        // Unit should be quoted uppercase string
16244        if self.config.dialect == Some(DialectType::Redshift)
16245            && upper_name == "DATE_TRUNC"
16246            && func.args.len() == 2
16247        {
16248            self.write_keyword("DATE_TRUNC");
16249            self.write("(");
16250            // First arg is unit - normalize to quoted uppercase
16251            self.write_redshift_date_part_quoted(&func.args[0]);
16252            self.write(", ");
16253            self.generate_expression(&func.args[1])?;
16254            self.write(")");
16255            return Ok(());
16256        }
16257
16258        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16259        if matches!(
16260            self.config.dialect,
16261            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16262        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16263            && func.args.len() == 2
16264        {
16265            self.write_keyword("DATEPART");
16266            self.write("(");
16267            self.generate_expression(&func.args[0])?;
16268            self.write(", ");
16269            self.generate_expression(&func.args[1])?;
16270            self.write(")");
16271            return Ok(());
16272        }
16273
16274        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16275        if matches!(
16276            self.config.dialect,
16277            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16278        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16279            && func.args.len() == 2
16280        {
16281            self.write_keyword("EXTRACT");
16282            self.write("(");
16283            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16284            match &func.args[0] {
16285                Expression::Literal(crate::expressions::Literal::String(s)) => {
16286                    self.write(&s.to_lowercase());
16287                }
16288                _ => self.generate_expression(&func.args[0])?,
16289            }
16290            self.write_space();
16291            self.write_keyword("FROM");
16292            self.write_space();
16293            self.generate_expression(&func.args[1])?;
16294            self.write(")");
16295            return Ok(());
16296        }
16297
16298        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16299        // Also DATE literals in Dremio should be CAST(...AS DATE)
16300        if self.config.dialect == Some(DialectType::Dremio)
16301            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16302            && func.args.len() == 2
16303        {
16304            self.write_keyword("EXTRACT");
16305            self.write("(");
16306            self.generate_expression(&func.args[0])?;
16307            self.write_space();
16308            self.write_keyword("FROM");
16309            self.write_space();
16310            // For Dremio, DATE literals should become CAST('value' AS DATE)
16311            self.generate_dremio_date_expression(&func.args[1])?;
16312            self.write(")");
16313            return Ok(());
16314        }
16315
16316        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16317        if self.config.dialect == Some(DialectType::Dremio)
16318            && upper_name == "CURRENT_DATE_UTC"
16319            && func.args.is_empty()
16320        {
16321            self.write_keyword("CURRENT_DATE_UTC");
16322            return Ok(());
16323        }
16324
16325        // Dremio: DATETYPE(year, month, day) transformation
16326        // - If all args are integer literals: DATE('YYYY-MM-DD')
16327        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16328        if self.config.dialect == Some(DialectType::Dremio)
16329            && upper_name == "DATETYPE"
16330            && func.args.len() == 3
16331        {
16332            // Helper function to extract integer from number literal
16333            fn get_int_literal(expr: &Expression) -> Option<i64> {
16334                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16335                    s.parse::<i64>().ok()
16336                } else {
16337                    None
16338                }
16339            }
16340
16341            // Check if all arguments are integer literals
16342            if let (Some(year), Some(month), Some(day)) = (
16343                get_int_literal(&func.args[0]),
16344                get_int_literal(&func.args[1]),
16345                get_int_literal(&func.args[2]),
16346            ) {
16347                // All are integer literals: DATE('YYYY-MM-DD')
16348                self.write_keyword("DATE");
16349                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16350                return Ok(());
16351            }
16352
16353            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16354            self.write_keyword("CAST");
16355            self.write("(");
16356            self.write_keyword("CONCAT");
16357            self.write("(");
16358            self.generate_expression(&func.args[0])?;
16359            self.write(", '-', ");
16360            self.generate_expression(&func.args[1])?;
16361            self.write(", '-', ");
16362            self.generate_expression(&func.args[2])?;
16363            self.write(")");
16364            self.write_space();
16365            self.write_keyword("AS");
16366            self.write_space();
16367            self.write_keyword("DATE");
16368            self.write(")");
16369            return Ok(());
16370        }
16371
16372        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16373        // when it's not an integer literal
16374        let is_presto_like = matches!(
16375            self.config.dialect,
16376            Some(DialectType::Presto) | Some(DialectType::Trino)
16377        );
16378        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16379            self.write_keyword("DATE_ADD");
16380            self.write("(");
16381            // First arg: unit (pass through as-is, e.g., 'DAY')
16382            self.generate_expression(&func.args[0])?;
16383            self.write(", ");
16384            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16385            let interval = &func.args[1];
16386            let needs_cast = !self.returns_integer_type(interval);
16387            if needs_cast {
16388                self.write_keyword("CAST");
16389                self.write("(");
16390            }
16391            self.generate_expression(interval)?;
16392            if needs_cast {
16393                self.write_space();
16394                self.write_keyword("AS");
16395                self.write_space();
16396                self.write_keyword("BIGINT");
16397                self.write(")");
16398            }
16399            self.write(", ");
16400            // Third arg: date
16401            self.generate_expression(&func.args[2])?;
16402            self.write(")");
16403            return Ok(());
16404        }
16405
16406        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16407        let use_brackets = func.use_bracket_syntax;
16408
16409        // Special case: functions WITH ORDINALITY need special output order
16410        // Input: FUNC(args) WITH ORDINALITY
16411        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16412        // Output must be: FUNC(args) WITH ORDINALITY
16413        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16414        let output_name = if has_ordinality {
16415            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16416            self.normalize_func_name(base_name)
16417        } else {
16418            normalized_name.clone()
16419        };
16420
16421        // For qualified names (schema.function or object.method), preserve original case
16422        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16423        if func.name.contains('.') && !has_ordinality {
16424            // Don't normalize qualified functions - preserve original case
16425            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16426            if func.quoted {
16427                self.write("`");
16428                self.write(&func.name);
16429                self.write("`");
16430            } else {
16431                self.write(&func.name);
16432            }
16433        } else {
16434            self.write(&output_name);
16435        }
16436
16437        // If no_parens is true and there are no args, output just the function name
16438        // Unless the target dialect requires parens for this function
16439        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16440            let needs_parens = match upper_name.as_str() {
16441                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16442                    self.config.dialect,
16443                    Some(DialectType::Snowflake)
16444                        | Some(DialectType::Spark)
16445                        | Some(DialectType::Databricks)
16446                        | Some(DialectType::Hive)
16447                ),
16448                _ => false,
16449            };
16450            !needs_parens
16451        };
16452        if force_parens {
16453            // Output trailing comments
16454            for comment in &func.trailing_comments {
16455                self.write_space();
16456                self.write_formatted_comment(comment);
16457            }
16458            return Ok(());
16459        }
16460
16461        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16462        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16463            self.write(" (");
16464        } else if use_brackets {
16465            self.write("[");
16466        } else {
16467            self.write("(");
16468        }
16469        if func.distinct {
16470            self.write_keyword("DISTINCT");
16471            self.write_space();
16472        }
16473
16474        // Check if arguments should be split onto multiple lines (pretty + too wide)
16475        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16476            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16477        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16478        let is_grouping_func =
16479            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16480        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16481            if is_grouping_func {
16482                true
16483            } else {
16484                // Pre-render arguments to check total width
16485                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16486                for arg in &func.args {
16487                    let mut temp_gen = Generator::with_config(self.config.clone());
16488                    temp_gen.config.pretty = false; // Don't recurse into pretty
16489                    temp_gen.generate_expression(arg)?;
16490                    expr_strings.push(temp_gen.output);
16491                }
16492                self.too_wide(&expr_strings)
16493            }
16494        } else {
16495            false
16496        };
16497
16498        if should_split {
16499            // Split onto multiple lines
16500            self.write_newline();
16501            self.indent_level += 1;
16502            for (i, arg) in func.args.iter().enumerate() {
16503                self.write_indent();
16504                self.generate_expression(arg)?;
16505                if i + 1 < func.args.len() {
16506                    self.write(",");
16507                }
16508                self.write_newline();
16509            }
16510            self.indent_level -= 1;
16511            self.write_indent();
16512        } else {
16513            // All on one line
16514            for (i, arg) in func.args.iter().enumerate() {
16515                if i > 0 {
16516                    self.write(", ");
16517                }
16518                self.generate_expression(arg)?;
16519            }
16520        }
16521
16522        if use_brackets {
16523            self.write("]");
16524        } else {
16525            self.write(")");
16526        }
16527        // Append WITH ORDINALITY after closing paren for table-valued functions
16528        if has_ordinality {
16529            self.write_space();
16530            self.write_keyword("WITH ORDINALITY");
16531        }
16532        // Output trailing comments
16533        for comment in &func.trailing_comments {
16534            self.write_space();
16535            self.write_formatted_comment(comment);
16536        }
16537        Ok(())
16538    }
16539
16540    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16541        // Normalize function name based on dialect settings
16542        let mut normalized_name = self.normalize_func_name(&func.name);
16543
16544        // Dialect-specific name mappings for aggregate functions
16545        let upper = normalized_name.to_uppercase();
16546        if upper == "MAX_BY" || upper == "MIN_BY" {
16547            let is_max = upper == "MAX_BY";
16548            match self.config.dialect {
16549                Some(DialectType::ClickHouse) => {
16550                    normalized_name = if is_max {
16551                        "argMax".to_string()
16552                    } else {
16553                        "argMin".to_string()
16554                    };
16555                }
16556                Some(DialectType::DuckDB) => {
16557                    normalized_name = if is_max {
16558                        "ARG_MAX".to_string()
16559                    } else {
16560                        "ARG_MIN".to_string()
16561                    };
16562                }
16563                _ => {}
16564            }
16565        }
16566        self.write(&normalized_name);
16567        self.write("(");
16568        if func.distinct {
16569            self.write_keyword("DISTINCT");
16570            self.write_space();
16571        }
16572
16573        // Check if we need to transform multi-arg COUNT DISTINCT
16574        // When dialect doesn't support multi_arg_distinct, transform:
16575        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16576        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16577        let needs_multi_arg_transform =
16578            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16579
16580        if needs_multi_arg_transform {
16581            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16582            self.write_keyword("CASE");
16583            for arg in &func.args {
16584                self.write_space();
16585                self.write_keyword("WHEN");
16586                self.write_space();
16587                self.generate_expression(arg)?;
16588                self.write_space();
16589                self.write_keyword("IS NULL THEN NULL");
16590            }
16591            self.write_space();
16592            self.write_keyword("ELSE");
16593            self.write(" (");
16594            for (i, arg) in func.args.iter().enumerate() {
16595                if i > 0 {
16596                    self.write(", ");
16597                }
16598                self.generate_expression(arg)?;
16599            }
16600            self.write(")");
16601            self.write_space();
16602            self.write_keyword("END");
16603        } else {
16604            for (i, arg) in func.args.iter().enumerate() {
16605                if i > 0 {
16606                    self.write(", ");
16607                }
16608                self.generate_expression(arg)?;
16609            }
16610        }
16611
16612        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16613        if self.config.ignore_nulls_in_func
16614            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16615        {
16616            if let Some(ignore) = func.ignore_nulls {
16617                self.write_space();
16618                if ignore {
16619                    self.write_keyword("IGNORE NULLS");
16620                } else {
16621                    self.write_keyword("RESPECT NULLS");
16622                }
16623            }
16624        }
16625
16626        // ORDER BY inside aggregate
16627        if !func.order_by.is_empty() {
16628            self.write_space();
16629            self.write_keyword("ORDER BY");
16630            self.write_space();
16631            for (i, ord) in func.order_by.iter().enumerate() {
16632                if i > 0 {
16633                    self.write(", ");
16634                }
16635                self.generate_ordered(ord)?;
16636            }
16637        }
16638
16639        // LIMIT inside aggregate
16640        if let Some(limit) = &func.limit {
16641            self.write_space();
16642            self.write_keyword("LIMIT");
16643            self.write_space();
16644            // Check if this is a Tuple representing LIMIT offset, count
16645            if let Expression::Tuple(t) = limit.as_ref() {
16646                if t.expressions.len() == 2 {
16647                    self.generate_expression(&t.expressions[0])?;
16648                    self.write(", ");
16649                    self.generate_expression(&t.expressions[1])?;
16650                } else {
16651                    self.generate_expression(limit)?;
16652                }
16653            } else {
16654                self.generate_expression(limit)?;
16655            }
16656        }
16657
16658        self.write(")");
16659
16660        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16661        if !self.config.ignore_nulls_in_func
16662            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16663        {
16664            if let Some(ignore) = func.ignore_nulls {
16665                self.write_space();
16666                if ignore {
16667                    self.write_keyword("IGNORE NULLS");
16668                } else {
16669                    self.write_keyword("RESPECT NULLS");
16670                }
16671            }
16672        }
16673
16674        if let Some(filter) = &func.filter {
16675            self.write_space();
16676            self.write_keyword("FILTER");
16677            self.write("(");
16678            self.write_keyword("WHERE");
16679            self.write_space();
16680            self.generate_expression(filter)?;
16681            self.write(")");
16682        }
16683
16684        Ok(())
16685    }
16686
16687    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16688        self.generate_expression(&wf.this)?;
16689
16690        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16691        if let Some(keep) = &wf.keep {
16692            self.write_space();
16693            self.write_keyword("KEEP");
16694            self.write(" (");
16695            self.write_keyword("DENSE_RANK");
16696            self.write_space();
16697            if keep.first {
16698                self.write_keyword("FIRST");
16699            } else {
16700                self.write_keyword("LAST");
16701            }
16702            self.write_space();
16703            self.write_keyword("ORDER BY");
16704            self.write_space();
16705            for (i, ord) in keep.order_by.iter().enumerate() {
16706                if i > 0 {
16707                    self.write(", ");
16708                }
16709                self.generate_ordered(ord)?;
16710            }
16711            self.write(")");
16712        }
16713
16714        // Check if there's any OVER clause content
16715        let has_over = !wf.over.partition_by.is_empty()
16716            || !wf.over.order_by.is_empty()
16717            || wf.over.frame.is_some()
16718            || wf.over.window_name.is_some();
16719
16720        // Only output OVER if there's actual window specification (not just KEEP alone)
16721        if has_over {
16722            self.write_space();
16723            self.write_keyword("OVER");
16724
16725            // Check if this is just a bare named window reference (no parens needed)
16726            let has_specs = !wf.over.partition_by.is_empty()
16727                || !wf.over.order_by.is_empty()
16728                || wf.over.frame.is_some();
16729
16730            if wf.over.window_name.is_some() && !has_specs {
16731                // OVER window_name (without parentheses)
16732                self.write_space();
16733                self.write(&wf.over.window_name.as_ref().unwrap().name);
16734            } else {
16735                // OVER (...) or OVER (window_name ...)
16736                self.write(" (");
16737                self.generate_over(&wf.over)?;
16738                self.write(")");
16739            }
16740        } else if wf.keep.is_none() {
16741            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16742            self.write_space();
16743            self.write_keyword("OVER");
16744            self.write(" ()");
16745        }
16746
16747        Ok(())
16748    }
16749
16750    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16751    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16752        self.generate_expression(&wg.this)?;
16753        self.write_space();
16754        self.write_keyword("WITHIN GROUP");
16755        self.write(" (");
16756        self.write_keyword("ORDER BY");
16757        self.write_space();
16758        for (i, ord) in wg.order_by.iter().enumerate() {
16759            if i > 0 {
16760                self.write(", ");
16761            }
16762            self.generate_ordered(ord)?;
16763        }
16764        self.write(")");
16765        Ok(())
16766    }
16767
16768    /// Generate the contents of an OVER clause (without parentheses)
16769    fn generate_over(&mut self, over: &Over) -> Result<()> {
16770        let mut has_content = false;
16771
16772        // Named window reference
16773        if let Some(name) = &over.window_name {
16774            self.write(&name.name);
16775            has_content = true;
16776        }
16777
16778        // PARTITION BY
16779        if !over.partition_by.is_empty() {
16780            if has_content {
16781                self.write_space();
16782            }
16783            self.write_keyword("PARTITION BY");
16784            self.write_space();
16785            for (i, expr) in over.partition_by.iter().enumerate() {
16786                if i > 0 {
16787                    self.write(", ");
16788                }
16789                self.generate_expression(expr)?;
16790            }
16791            has_content = true;
16792        }
16793
16794        // ORDER BY
16795        if !over.order_by.is_empty() {
16796            if has_content {
16797                self.write_space();
16798            }
16799            self.write_keyword("ORDER BY");
16800            self.write_space();
16801            for (i, ordered) in over.order_by.iter().enumerate() {
16802                if i > 0 {
16803                    self.write(", ");
16804                }
16805                self.generate_ordered(ordered)?;
16806            }
16807            has_content = true;
16808        }
16809
16810        // Window frame
16811        if let Some(frame) = &over.frame {
16812            if has_content {
16813                self.write_space();
16814            }
16815            self.generate_window_frame(frame)?;
16816        }
16817
16818        Ok(())
16819    }
16820
16821    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16822        // Exasol uses lowercase for frame kind (rows/range/groups)
16823        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16824
16825        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16826        if !lowercase_frame {
16827            if let Some(kind_text) = &frame.kind_text {
16828                self.write(kind_text);
16829            } else {
16830                match frame.kind {
16831                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16832                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16833                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16834                }
16835            }
16836        } else {
16837            match frame.kind {
16838                WindowFrameKind::Rows => self.write("rows"),
16839                WindowFrameKind::Range => self.write("range"),
16840                WindowFrameKind::Groups => self.write("groups"),
16841            }
16842        }
16843
16844        // Use BETWEEN format only when there's an explicit end bound,
16845        // or when normalize_window_frame_between is enabled and the start is a directional bound
16846        self.write_space();
16847        let should_normalize = self.config.normalize_window_frame_between
16848            && frame.end.is_none()
16849            && matches!(
16850                frame.start,
16851                WindowFrameBound::Preceding(_)
16852                    | WindowFrameBound::Following(_)
16853                    | WindowFrameBound::UnboundedPreceding
16854                    | WindowFrameBound::UnboundedFollowing
16855            );
16856
16857        if let Some(end) = &frame.end {
16858            // BETWEEN format: RANGE BETWEEN start AND end
16859            self.write_keyword("BETWEEN");
16860            self.write_space();
16861            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16862            self.write_space();
16863            self.write_keyword("AND");
16864            self.write_space();
16865            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16866        } else if should_normalize {
16867            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16868            self.write_keyword("BETWEEN");
16869            self.write_space();
16870            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16871            self.write_space();
16872            self.write_keyword("AND");
16873            self.write_space();
16874            self.write_keyword("CURRENT ROW");
16875        } else {
16876            // Single bound format: RANGE CURRENT ROW
16877            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16878        }
16879
16880        // EXCLUDE clause
16881        if let Some(exclude) = &frame.exclude {
16882            self.write_space();
16883            self.write_keyword("EXCLUDE");
16884            self.write_space();
16885            match exclude {
16886                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16887                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16888                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16889                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16890            }
16891        }
16892
16893        Ok(())
16894    }
16895
16896    fn generate_window_frame_bound(
16897        &mut self,
16898        bound: &WindowFrameBound,
16899        side_text: Option<&str>,
16900    ) -> Result<()> {
16901        // Exasol uses lowercase for preceding/following
16902        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16903
16904        match bound {
16905            WindowFrameBound::CurrentRow => {
16906                self.write_keyword("CURRENT ROW");
16907            }
16908            WindowFrameBound::UnboundedPreceding => {
16909                self.write_keyword("UNBOUNDED");
16910                self.write_space();
16911                if lowercase_frame {
16912                    self.write("preceding");
16913                } else if let Some(text) = side_text {
16914                    self.write(text);
16915                } else {
16916                    self.write_keyword("PRECEDING");
16917                }
16918            }
16919            WindowFrameBound::UnboundedFollowing => {
16920                self.write_keyword("UNBOUNDED");
16921                self.write_space();
16922                if lowercase_frame {
16923                    self.write("following");
16924                } else if let Some(text) = side_text {
16925                    self.write(text);
16926                } else {
16927                    self.write_keyword("FOLLOWING");
16928                }
16929            }
16930            WindowFrameBound::Preceding(expr) => {
16931                self.generate_expression(expr)?;
16932                self.write_space();
16933                if lowercase_frame {
16934                    self.write("preceding");
16935                } else if let Some(text) = side_text {
16936                    self.write(text);
16937                } else {
16938                    self.write_keyword("PRECEDING");
16939                }
16940            }
16941            WindowFrameBound::Following(expr) => {
16942                self.generate_expression(expr)?;
16943                self.write_space();
16944                if lowercase_frame {
16945                    self.write("following");
16946                } else if let Some(text) = side_text {
16947                    self.write(text);
16948                } else {
16949                    self.write_keyword("FOLLOWING");
16950                }
16951            }
16952            WindowFrameBound::BarePreceding => {
16953                if lowercase_frame {
16954                    self.write("preceding");
16955                } else if let Some(text) = side_text {
16956                    self.write(text);
16957                } else {
16958                    self.write_keyword("PRECEDING");
16959                }
16960            }
16961            WindowFrameBound::BareFollowing => {
16962                if lowercase_frame {
16963                    self.write("following");
16964                } else if let Some(text) = side_text {
16965                    self.write(text);
16966                } else {
16967                    self.write_keyword("FOLLOWING");
16968                }
16969            }
16970            WindowFrameBound::Value(expr) => {
16971                // Bare numeric bound without PRECEDING/FOLLOWING
16972                self.generate_expression(expr)?;
16973            }
16974        }
16975        Ok(())
16976    }
16977
16978    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16979        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16980        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16981        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16982            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16983            && !matches!(&interval.this, Some(Expression::Literal(_)));
16984
16985        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16986        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16987        if self.config.single_string_interval {
16988            if let (
16989                Some(Expression::Literal(Literal::String(ref val))),
16990                Some(IntervalUnitSpec::Simple {
16991                    ref unit,
16992                    ref use_plural,
16993                }),
16994            ) = (&interval.this, &interval.unit)
16995            {
16996                self.write_keyword("INTERVAL");
16997                self.write_space();
16998                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
16999                let unit_str = self.interval_unit_str(unit, effective_plural);
17000                self.write("'");
17001                self.write(val);
17002                self.write(" ");
17003                self.write(&unit_str);
17004                self.write("'");
17005                return Ok(());
17006            }
17007        }
17008
17009        if !skip_interval_keyword {
17010            self.write_keyword("INTERVAL");
17011        }
17012
17013        // Generate value if present
17014        if let Some(ref value) = interval.this {
17015            if !skip_interval_keyword {
17016                self.write_space();
17017            }
17018            // If the value is a complex expression (not a literal/column/function call)
17019            // and there's a unit, wrap it in parentheses
17020            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17021            let needs_parens = interval.unit.is_some()
17022                && matches!(
17023                    value,
17024                    Expression::Add(_)
17025                        | Expression::Sub(_)
17026                        | Expression::Mul(_)
17027                        | Expression::Div(_)
17028                        | Expression::Mod(_)
17029                        | Expression::BitwiseAnd(_)
17030                        | Expression::BitwiseOr(_)
17031                        | Expression::BitwiseXor(_)
17032                );
17033            if needs_parens {
17034                self.write("(");
17035            }
17036            self.generate_expression(value)?;
17037            if needs_parens {
17038                self.write(")");
17039            }
17040        }
17041
17042        // Generate unit if present
17043        if let Some(ref unit_spec) = interval.unit {
17044            self.write_space();
17045            self.write_interval_unit_spec(unit_spec)?;
17046        }
17047
17048        Ok(())
17049    }
17050
17051    /// Return the string representation of an interval unit
17052    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17053        match (unit, use_plural) {
17054            (IntervalUnit::Year, false) => "YEAR",
17055            (IntervalUnit::Year, true) => "YEARS",
17056            (IntervalUnit::Quarter, false) => "QUARTER",
17057            (IntervalUnit::Quarter, true) => "QUARTERS",
17058            (IntervalUnit::Month, false) => "MONTH",
17059            (IntervalUnit::Month, true) => "MONTHS",
17060            (IntervalUnit::Week, false) => "WEEK",
17061            (IntervalUnit::Week, true) => "WEEKS",
17062            (IntervalUnit::Day, false) => "DAY",
17063            (IntervalUnit::Day, true) => "DAYS",
17064            (IntervalUnit::Hour, false) => "HOUR",
17065            (IntervalUnit::Hour, true) => "HOURS",
17066            (IntervalUnit::Minute, false) => "MINUTE",
17067            (IntervalUnit::Minute, true) => "MINUTES",
17068            (IntervalUnit::Second, false) => "SECOND",
17069            (IntervalUnit::Second, true) => "SECONDS",
17070            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17071            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17072            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17073            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17074            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17075            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17076        }
17077    }
17078
17079    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17080        match unit_spec {
17081            IntervalUnitSpec::Simple { unit, use_plural } => {
17082                // If dialect doesn't allow plural forms, force singular
17083                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17084                self.write_simple_interval_unit(unit, effective_plural);
17085            }
17086            IntervalUnitSpec::Span(span) => {
17087                self.write_simple_interval_unit(&span.this, false);
17088                self.write_space();
17089                self.write_keyword("TO");
17090                self.write_space();
17091                self.write_simple_interval_unit(&span.expression, false);
17092            }
17093            IntervalUnitSpec::ExprSpan(span) => {
17094                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17095                self.generate_expression(&span.this)?;
17096                self.write_space();
17097                self.write_keyword("TO");
17098                self.write_space();
17099                self.generate_expression(&span.expression)?;
17100            }
17101            IntervalUnitSpec::Expr(expr) => {
17102                self.generate_expression(expr)?;
17103            }
17104        }
17105        Ok(())
17106    }
17107
17108    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17109        // Output interval unit, respecting plural preference
17110        match (unit, use_plural) {
17111            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17112            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17113            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17114            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17115            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17116            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17117            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17118            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17119            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17120            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17121            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17122            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17123            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17124            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17125            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17126            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17127            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17128            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17129            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17130            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17131            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17132            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17133        }
17134    }
17135
17136    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17137    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17138    fn write_redshift_date_part(&mut self, expr: &Expression) {
17139        let part_str = self.extract_date_part_string(expr);
17140        if let Some(part) = part_str {
17141            let normalized = self.normalize_date_part(&part);
17142            self.write_keyword(&normalized);
17143        } else {
17144            // If we can't extract a date part string, fall back to generating the expression
17145            let _ = self.generate_expression(expr);
17146        }
17147    }
17148
17149    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17150    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17151    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17152        let part_str = self.extract_date_part_string(expr);
17153        if let Some(part) = part_str {
17154            let normalized = self.normalize_date_part(&part);
17155            self.write("'");
17156            self.write(&normalized);
17157            self.write("'");
17158        } else {
17159            // If we can't extract a date part string, fall back to generating the expression
17160            let _ = self.generate_expression(expr);
17161        }
17162    }
17163
17164    /// Extract date part string from expression (handles string literals and identifiers)
17165    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17166        match expr {
17167            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17168            Expression::Identifier(id) => Some(id.name.clone()),
17169            Expression::Column(col) if col.table.is_none() => {
17170                // Simple column reference without table prefix, treat as identifier
17171                Some(col.name.name.clone())
17172            }
17173            _ => None,
17174        }
17175    }
17176
17177    /// Normalize date part to uppercase singular form
17178    /// days -> DAY, months -> MONTH, etc.
17179    fn normalize_date_part(&self, part: &str) -> String {
17180        let lower = part.to_lowercase();
17181        match lower.as_str() {
17182            "day" | "days" | "d" => "DAY".to_string(),
17183            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17184            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17185            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17186            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17187            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17188            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17189            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17190            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17191            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17192            _ => part.to_uppercase(),
17193        }
17194    }
17195
17196    fn write_datetime_field(&mut self, field: &DateTimeField) {
17197        match field {
17198            DateTimeField::Year => self.write_keyword("YEAR"),
17199            DateTimeField::Month => self.write_keyword("MONTH"),
17200            DateTimeField::Day => self.write_keyword("DAY"),
17201            DateTimeField::Hour => self.write_keyword("HOUR"),
17202            DateTimeField::Minute => self.write_keyword("MINUTE"),
17203            DateTimeField::Second => self.write_keyword("SECOND"),
17204            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17205            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17206            DateTimeField::DayOfWeek => {
17207                let name = match self.config.dialect {
17208                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17209                    _ => "DOW",
17210                };
17211                self.write_keyword(name);
17212            }
17213            DateTimeField::DayOfYear => {
17214                let name = match self.config.dialect {
17215                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17216                    _ => "DOY",
17217                };
17218                self.write_keyword(name);
17219            }
17220            DateTimeField::Week => self.write_keyword("WEEK"),
17221            DateTimeField::WeekWithModifier(modifier) => {
17222                self.write_keyword("WEEK");
17223                self.write("(");
17224                self.write(modifier);
17225                self.write(")");
17226            }
17227            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17228            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17229            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17230            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17231            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17232            DateTimeField::Date => self.write_keyword("DATE"),
17233            DateTimeField::Time => self.write_keyword("TIME"),
17234            DateTimeField::Custom(name) => self.write(name),
17235        }
17236    }
17237
17238    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17239    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17240        match field {
17241            DateTimeField::Year => self.write("year"),
17242            DateTimeField::Month => self.write("month"),
17243            DateTimeField::Day => self.write("day"),
17244            DateTimeField::Hour => self.write("hour"),
17245            DateTimeField::Minute => self.write("minute"),
17246            DateTimeField::Second => self.write("second"),
17247            DateTimeField::Millisecond => self.write("millisecond"),
17248            DateTimeField::Microsecond => self.write("microsecond"),
17249            DateTimeField::DayOfWeek => self.write("dow"),
17250            DateTimeField::DayOfYear => self.write("doy"),
17251            DateTimeField::Week => self.write("week"),
17252            DateTimeField::WeekWithModifier(modifier) => {
17253                self.write("week(");
17254                self.write(modifier);
17255                self.write(")");
17256            }
17257            DateTimeField::Quarter => self.write("quarter"),
17258            DateTimeField::Epoch => self.write("epoch"),
17259            DateTimeField::Timezone => self.write("timezone"),
17260            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17261            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17262            DateTimeField::Date => self.write("date"),
17263            DateTimeField::Time => self.write("time"),
17264            DateTimeField::Custom(name) => self.write(name),
17265        }
17266    }
17267
17268    // Helper function generators
17269
17270    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17271        self.write_keyword(name);
17272        self.write("(");
17273        self.generate_expression(arg)?;
17274        self.write(")");
17275        Ok(())
17276    }
17277
17278    /// Generate a unary function, using the original name if available for round-trip preservation
17279    fn generate_unary_func(
17280        &mut self,
17281        default_name: &str,
17282        f: &crate::expressions::UnaryFunc,
17283    ) -> Result<()> {
17284        let name = f.original_name.as_deref().unwrap_or(default_name);
17285        self.write_keyword(name);
17286        self.write("(");
17287        self.generate_expression(&f.this)?;
17288        self.write(")");
17289        Ok(())
17290    }
17291
17292    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17293    fn generate_sqrt_cbrt(
17294        &mut self,
17295        f: &crate::expressions::UnaryFunc,
17296        func_name: &str,
17297        _op: &str,
17298    ) -> Result<()> {
17299        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17300        // Always use function syntax for consistency
17301        self.write_keyword(func_name);
17302        self.write("(");
17303        self.generate_expression(&f.this)?;
17304        self.write(")");
17305        Ok(())
17306    }
17307
17308    fn generate_binary_func(
17309        &mut self,
17310        name: &str,
17311        arg1: &Expression,
17312        arg2: &Expression,
17313    ) -> Result<()> {
17314        self.write_keyword(name);
17315        self.write("(");
17316        self.generate_expression(arg1)?;
17317        self.write(", ");
17318        self.generate_expression(arg2)?;
17319        self.write(")");
17320        Ok(())
17321    }
17322
17323    /// Generate CHAR/CHR function with optional USING charset
17324    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17325    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17326    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17327        // Use stored name if available, otherwise default to CHAR
17328        let func_name = f.name.as_deref().unwrap_or("CHAR");
17329        self.write_keyword(func_name);
17330        self.write("(");
17331        for (i, arg) in f.args.iter().enumerate() {
17332            if i > 0 {
17333                self.write(", ");
17334            }
17335            self.generate_expression(arg)?;
17336        }
17337        if let Some(ref charset) = f.charset {
17338            self.write(" ");
17339            self.write_keyword("USING");
17340            self.write(" ");
17341            self.write(charset);
17342        }
17343        self.write(")");
17344        Ok(())
17345    }
17346
17347    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17348        use crate::dialects::DialectType;
17349
17350        match self.config.dialect {
17351            Some(DialectType::Teradata) => {
17352                // Teradata uses ** operator for exponentiation
17353                self.generate_expression(&f.this)?;
17354                self.write(" ** ");
17355                self.generate_expression(&f.expression)?;
17356                Ok(())
17357            }
17358            _ => {
17359                // Other dialects use POWER function
17360                self.generate_binary_func("POWER", &f.this, &f.expression)
17361            }
17362        }
17363    }
17364
17365    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17366        self.write_func_name(name);
17367        self.write("(");
17368        for (i, arg) in args.iter().enumerate() {
17369            if i > 0 {
17370                self.write(", ");
17371            }
17372            self.generate_expression(arg)?;
17373        }
17374        self.write(")");
17375        Ok(())
17376    }
17377
17378    // String function generators
17379
17380    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17381        self.write_keyword("CONCAT_WS");
17382        self.write("(");
17383        self.generate_expression(&f.separator)?;
17384        for expr in &f.expressions {
17385            self.write(", ");
17386            self.generate_expression(expr)?;
17387        }
17388        self.write(")");
17389        Ok(())
17390    }
17391
17392    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17393        if let Expression::Concat(op) = expr {
17394            Self::collect_concat_operands(&op.left, out);
17395            Self::collect_concat_operands(&op.right, out);
17396        } else {
17397            out.push(expr);
17398        }
17399    }
17400
17401    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
17402        let mut operands = Vec::new();
17403        Self::collect_concat_operands(&op.left, &mut operands);
17404        Self::collect_concat_operands(&op.right, &mut operands);
17405
17406        self.write_keyword("CONCAT");
17407        self.write("(");
17408        for (i, operand) in operands.iter().enumerate() {
17409            if i > 0 {
17410                self.write(", ");
17411            }
17412            self.generate_expression(operand)?;
17413        }
17414        self.write(")");
17415        Ok(())
17416    }
17417
17418    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17419        if let Expression::DPipe(dpipe) = expr {
17420            Self::collect_dpipe_operands(&dpipe.this, out);
17421            Self::collect_dpipe_operands(&dpipe.expression, out);
17422        } else {
17423            out.push(expr);
17424        }
17425    }
17426
17427    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
17428        let mut operands = Vec::new();
17429        Self::collect_dpipe_operands(&e.this, &mut operands);
17430        Self::collect_dpipe_operands(&e.expression, &mut operands);
17431
17432        self.write_keyword("CONCAT");
17433        self.write("(");
17434        for (i, operand) in operands.iter().enumerate() {
17435            if i > 0 {
17436                self.write(", ");
17437            }
17438            self.generate_expression(operand)?;
17439        }
17440        self.write(")");
17441        Ok(())
17442    }
17443
17444    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17445        // Oracle uses SUBSTR; most others use SUBSTRING
17446        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17447        if is_oracle {
17448            self.write_keyword("SUBSTR");
17449        } else {
17450            self.write_keyword("SUBSTRING");
17451        }
17452        self.write("(");
17453        self.generate_expression(&f.this)?;
17454        // PostgreSQL always uses FROM/FOR syntax
17455        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17456        // Spark/Hive use comma syntax, not FROM/FOR syntax
17457        let use_comma_syntax = matches!(
17458            self.config.dialect,
17459            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17460        );
17461        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17462            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17463            self.write_space();
17464            self.write_keyword("FROM");
17465            self.write_space();
17466            self.generate_expression(&f.start)?;
17467            if let Some(length) = &f.length {
17468                self.write_space();
17469                self.write_keyword("FOR");
17470                self.write_space();
17471                self.generate_expression(length)?;
17472            }
17473        } else {
17474            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17475            self.write(", ");
17476            self.generate_expression(&f.start)?;
17477            if let Some(length) = &f.length {
17478                self.write(", ");
17479                self.generate_expression(length)?;
17480            }
17481        }
17482        self.write(")");
17483        Ok(())
17484    }
17485
17486    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17487        self.write_keyword("OVERLAY");
17488        self.write("(");
17489        self.generate_expression(&f.this)?;
17490        self.write_space();
17491        self.write_keyword("PLACING");
17492        self.write_space();
17493        self.generate_expression(&f.replacement)?;
17494        self.write_space();
17495        self.write_keyword("FROM");
17496        self.write_space();
17497        self.generate_expression(&f.from)?;
17498        if let Some(length) = &f.length {
17499            self.write_space();
17500            self.write_keyword("FOR");
17501            self.write_space();
17502            self.generate_expression(length)?;
17503        }
17504        self.write(")");
17505        Ok(())
17506    }
17507
17508    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17509        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17510        // when no characters are specified (PostgreSQL style)
17511        if f.position_explicit && f.characters.is_none() {
17512            match f.position {
17513                TrimPosition::Leading => {
17514                    self.write_keyword("LTRIM");
17515                    self.write("(");
17516                    self.generate_expression(&f.this)?;
17517                    self.write(")");
17518                    return Ok(());
17519                }
17520                TrimPosition::Trailing => {
17521                    self.write_keyword("RTRIM");
17522                    self.write("(");
17523                    self.generate_expression(&f.this)?;
17524                    self.write(")");
17525                    return Ok(());
17526                }
17527                TrimPosition::Both => {
17528                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17529                    // Fall through to standard TRIM handling
17530                }
17531            }
17532        }
17533
17534        self.write_keyword("TRIM");
17535        self.write("(");
17536        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17537        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17538        let force_standard = f.characters.is_some()
17539            && !f.sql_standard_syntax
17540            && matches!(
17541                self.config.dialect,
17542                Some(DialectType::Hive)
17543                    | Some(DialectType::Spark)
17544                    | Some(DialectType::Databricks)
17545                    | Some(DialectType::ClickHouse)
17546            );
17547        let use_standard = (f.sql_standard_syntax || force_standard)
17548            && !(f.position_explicit
17549                && f.characters.is_none()
17550                && matches!(f.position, TrimPosition::Both));
17551        if use_standard {
17552            // SQL standard syntax: TRIM(BOTH chars FROM str)
17553            // Only output position if it was explicitly specified
17554            if f.position_explicit {
17555                match f.position {
17556                    TrimPosition::Both => self.write_keyword("BOTH"),
17557                    TrimPosition::Leading => self.write_keyword("LEADING"),
17558                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17559                }
17560                self.write_space();
17561            }
17562            if let Some(chars) = &f.characters {
17563                self.generate_expression(chars)?;
17564                self.write_space();
17565            }
17566            self.write_keyword("FROM");
17567            self.write_space();
17568            self.generate_expression(&f.this)?;
17569        } else {
17570            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17571            self.generate_expression(&f.this)?;
17572            if let Some(chars) = &f.characters {
17573                self.write(", ");
17574                self.generate_expression(chars)?;
17575            }
17576        }
17577        self.write(")");
17578        Ok(())
17579    }
17580
17581    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17582        self.write_keyword("REPLACE");
17583        self.write("(");
17584        self.generate_expression(&f.this)?;
17585        self.write(", ");
17586        self.generate_expression(&f.old)?;
17587        self.write(", ");
17588        self.generate_expression(&f.new)?;
17589        self.write(")");
17590        Ok(())
17591    }
17592
17593    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17594        self.write_keyword(name);
17595        self.write("(");
17596        self.generate_expression(&f.this)?;
17597        self.write(", ");
17598        self.generate_expression(&f.length)?;
17599        self.write(")");
17600        Ok(())
17601    }
17602
17603    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17604        self.write_keyword("REPEAT");
17605        self.write("(");
17606        self.generate_expression(&f.this)?;
17607        self.write(", ");
17608        self.generate_expression(&f.times)?;
17609        self.write(")");
17610        Ok(())
17611    }
17612
17613    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17614        self.write_keyword(name);
17615        self.write("(");
17616        self.generate_expression(&f.this)?;
17617        self.write(", ");
17618        self.generate_expression(&f.length)?;
17619        if let Some(fill) = &f.fill {
17620            self.write(", ");
17621            self.generate_expression(fill)?;
17622        }
17623        self.write(")");
17624        Ok(())
17625    }
17626
17627    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17628        self.write_keyword("SPLIT");
17629        self.write("(");
17630        self.generate_expression(&f.this)?;
17631        self.write(", ");
17632        self.generate_expression(&f.delimiter)?;
17633        self.write(")");
17634        Ok(())
17635    }
17636
17637    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17638        use crate::dialects::DialectType;
17639        // PostgreSQL uses ~ operator for regex matching
17640        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17641            self.generate_expression(&f.this)?;
17642            self.write(" ~ ");
17643            self.generate_expression(&f.pattern)?;
17644        } else if matches!(
17645            self.config.dialect,
17646            Some(DialectType::SingleStore)
17647                | Some(DialectType::Spark)
17648                | Some(DialectType::Hive)
17649                | Some(DialectType::Databricks)
17650        ) && f.flags.is_none()
17651        {
17652            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17653            self.generate_expression(&f.this)?;
17654            self.write_keyword(" RLIKE ");
17655            self.generate_expression(&f.pattern)?;
17656        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17657            // StarRocks uses REGEXP function syntax
17658            self.write_keyword("REGEXP");
17659            self.write("(");
17660            self.generate_expression(&f.this)?;
17661            self.write(", ");
17662            self.generate_expression(&f.pattern)?;
17663            if let Some(flags) = &f.flags {
17664                self.write(", ");
17665                self.generate_expression(flags)?;
17666            }
17667            self.write(")");
17668        } else {
17669            self.write_keyword("REGEXP_LIKE");
17670            self.write("(");
17671            self.generate_expression(&f.this)?;
17672            self.write(", ");
17673            self.generate_expression(&f.pattern)?;
17674            if let Some(flags) = &f.flags {
17675                self.write(", ");
17676                self.generate_expression(flags)?;
17677            }
17678            self.write(")");
17679        }
17680        Ok(())
17681    }
17682
17683    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17684        self.write_keyword("REGEXP_REPLACE");
17685        self.write("(");
17686        self.generate_expression(&f.this)?;
17687        self.write(", ");
17688        self.generate_expression(&f.pattern)?;
17689        self.write(", ");
17690        self.generate_expression(&f.replacement)?;
17691        if let Some(flags) = &f.flags {
17692            self.write(", ");
17693            self.generate_expression(flags)?;
17694        }
17695        self.write(")");
17696        Ok(())
17697    }
17698
17699    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17700        self.write_keyword("REGEXP_EXTRACT");
17701        self.write("(");
17702        self.generate_expression(&f.this)?;
17703        self.write(", ");
17704        self.generate_expression(&f.pattern)?;
17705        if let Some(group) = &f.group {
17706            self.write(", ");
17707            self.generate_expression(group)?;
17708        }
17709        self.write(")");
17710        Ok(())
17711    }
17712
17713    // Math function generators
17714
17715    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17716        self.write_keyword("ROUND");
17717        self.write("(");
17718        self.generate_expression(&f.this)?;
17719        if let Some(decimals) = &f.decimals {
17720            self.write(", ");
17721            self.generate_expression(decimals)?;
17722        }
17723        self.write(")");
17724        Ok(())
17725    }
17726
17727    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17728        self.write_keyword("FLOOR");
17729        self.write("(");
17730        self.generate_expression(&f.this)?;
17731        // Handle Druid-style FLOOR(time TO unit) syntax
17732        if let Some(to) = &f.to {
17733            self.write(" ");
17734            self.write_keyword("TO");
17735            self.write(" ");
17736            self.generate_expression(to)?;
17737        } else if let Some(scale) = &f.scale {
17738            self.write(", ");
17739            self.generate_expression(scale)?;
17740        }
17741        self.write(")");
17742        Ok(())
17743    }
17744
17745    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17746        self.write_keyword("CEIL");
17747        self.write("(");
17748        self.generate_expression(&f.this)?;
17749        // Handle Druid-style CEIL(time TO unit) syntax
17750        if let Some(to) = &f.to {
17751            self.write(" ");
17752            self.write_keyword("TO");
17753            self.write(" ");
17754            self.generate_expression(to)?;
17755        } else if let Some(decimals) = &f.decimals {
17756            self.write(", ");
17757            self.generate_expression(decimals)?;
17758        }
17759        self.write(")");
17760        Ok(())
17761    }
17762
17763    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17764        use crate::expressions::Literal;
17765
17766        if let Some(base) = &f.base {
17767            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17768            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17769            if self.is_log_base_none() {
17770                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17771                    self.write_func_name("LOG2");
17772                    self.write("(");
17773                    self.generate_expression(&f.this)?;
17774                    self.write(")");
17775                    return Ok(());
17776                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17777                    self.write_func_name("LOG10");
17778                    self.write("(");
17779                    self.generate_expression(&f.this)?;
17780                    self.write(")");
17781                    return Ok(());
17782                }
17783                // Other bases: fall through to LOG(base, value) — best effort
17784            }
17785
17786            self.write_func_name("LOG");
17787            self.write("(");
17788            if self.is_log_value_first() {
17789                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17790                self.generate_expression(&f.this)?;
17791                self.write(", ");
17792                self.generate_expression(base)?;
17793            } else {
17794                // Default (PostgreSQL, etc.): LOG(base, value)
17795                self.generate_expression(base)?;
17796                self.write(", ");
17797                self.generate_expression(&f.this)?;
17798            }
17799            self.write(")");
17800        } else {
17801            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17802            self.write_func_name("LOG");
17803            self.write("(");
17804            self.generate_expression(&f.this)?;
17805            self.write(")");
17806        }
17807        Ok(())
17808    }
17809
17810    /// Whether the target dialect uses LOG(value, base) order (value first).
17811    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17812    fn is_log_value_first(&self) -> bool {
17813        use crate::dialects::DialectType;
17814        matches!(
17815            self.config.dialect,
17816            Some(DialectType::BigQuery)
17817                | Some(DialectType::TSQL)
17818                | Some(DialectType::Tableau)
17819                | Some(DialectType::Fabric)
17820        )
17821    }
17822
17823    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17824    /// Presto, Trino, ClickHouse, Athena.
17825    fn is_log_base_none(&self) -> bool {
17826        use crate::dialects::DialectType;
17827        matches!(
17828            self.config.dialect,
17829            Some(DialectType::Presto)
17830                | Some(DialectType::Trino)
17831                | Some(DialectType::ClickHouse)
17832                | Some(DialectType::Athena)
17833        )
17834    }
17835
17836    // Date/time function generators
17837
17838    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17839        self.write_keyword("CURRENT_TIME");
17840        if let Some(precision) = f.precision {
17841            self.write(&format!("({})", precision));
17842        }
17843        Ok(())
17844    }
17845
17846    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17847        use crate::dialects::DialectType;
17848
17849        // Oracle/Redshift SYSDATE handling
17850        if f.sysdate {
17851            match self.config.dialect {
17852                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17853                    self.write_keyword("SYSDATE");
17854                    return Ok(());
17855                }
17856                Some(DialectType::Snowflake) => {
17857                    // Snowflake uses SYSDATE() function
17858                    self.write_keyword("SYSDATE");
17859                    self.write("()");
17860                    return Ok(());
17861                }
17862                _ => {
17863                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17864                }
17865            }
17866        }
17867
17868        self.write_keyword("CURRENT_TIMESTAMP");
17869        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17870        if let Some(precision) = f.precision {
17871            self.write(&format!("({})", precision));
17872        } else if matches!(
17873            self.config.dialect,
17874            Some(crate::dialects::DialectType::MySQL)
17875                | Some(crate::dialects::DialectType::SingleStore)
17876                | Some(crate::dialects::DialectType::TiDB)
17877                | Some(crate::dialects::DialectType::Spark)
17878                | Some(crate::dialects::DialectType::Hive)
17879                | Some(crate::dialects::DialectType::Databricks)
17880                | Some(crate::dialects::DialectType::ClickHouse)
17881                | Some(crate::dialects::DialectType::BigQuery)
17882                | Some(crate::dialects::DialectType::Snowflake)
17883        ) {
17884            self.write("()");
17885        }
17886        Ok(())
17887    }
17888
17889    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17890        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17891        if self.config.dialect == Some(DialectType::Exasol) {
17892            self.write_keyword("CONVERT_TZ");
17893            self.write("(");
17894            self.generate_expression(&f.this)?;
17895            self.write(", 'UTC', ");
17896            self.generate_expression(&f.zone)?;
17897            self.write(")");
17898            return Ok(());
17899        }
17900
17901        self.generate_expression(&f.this)?;
17902        self.write_space();
17903        self.write_keyword("AT TIME ZONE");
17904        self.write_space();
17905        self.generate_expression(&f.zone)?;
17906        Ok(())
17907    }
17908
17909    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17910        use crate::dialects::DialectType;
17911
17912        // Presto/Trino use DATE_ADD('unit', interval, date) format
17913        // with the interval cast to BIGINT when needed
17914        let is_presto_like = matches!(
17915            self.config.dialect,
17916            Some(DialectType::Presto) | Some(DialectType::Trino)
17917        );
17918
17919        if is_presto_like {
17920            self.write_keyword(name);
17921            self.write("(");
17922            // Unit as string literal
17923            self.write("'");
17924            self.write_simple_interval_unit(&f.unit, false);
17925            self.write("'");
17926            self.write(", ");
17927            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17928            let needs_cast = !self.returns_integer_type(&f.interval);
17929            if needs_cast {
17930                self.write_keyword("CAST");
17931                self.write("(");
17932            }
17933            self.generate_expression(&f.interval)?;
17934            if needs_cast {
17935                self.write_space();
17936                self.write_keyword("AS");
17937                self.write_space();
17938                self.write_keyword("BIGINT");
17939                self.write(")");
17940            }
17941            self.write(", ");
17942            self.generate_expression(&f.this)?;
17943            self.write(")");
17944        } else {
17945            self.write_keyword(name);
17946            self.write("(");
17947            self.generate_expression(&f.this)?;
17948            self.write(", ");
17949            self.write_keyword("INTERVAL");
17950            self.write_space();
17951            self.generate_expression(&f.interval)?;
17952            self.write_space();
17953            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17954            self.write(")");
17955        }
17956        Ok(())
17957    }
17958
17959    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17960    /// This is a heuristic to avoid full type inference
17961    fn returns_integer_type(&self, expr: &Expression) -> bool {
17962        use crate::expressions::{DataType, Literal};
17963        match expr {
17964            // Integer literals (no decimal point)
17965            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17966
17967            // FLOOR(x) returns integer if x is integer
17968            Expression::Floor(f) => self.returns_integer_type(&f.this),
17969
17970            // ROUND(x) returns integer if x is integer
17971            Expression::Round(f) => {
17972                // Only if no decimals arg or it's returning an integer
17973                f.decimals.is_none() && self.returns_integer_type(&f.this)
17974            }
17975
17976            // SIGN returns integer if input is integer
17977            Expression::Sign(f) => self.returns_integer_type(&f.this),
17978
17979            // ABS returns the same type as input
17980            Expression::Abs(f) => self.returns_integer_type(&f.this),
17981
17982            // Arithmetic operations on integers return integers
17983            Expression::Mul(op) => {
17984                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17985            }
17986            Expression::Add(op) => {
17987                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17988            }
17989            Expression::Sub(op) => {
17990                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17991            }
17992            Expression::Mod(op) => self.returns_integer_type(&op.left),
17993
17994            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17995            Expression::Cast(c) => matches!(
17996                &c.to,
17997                DataType::BigInt { .. }
17998                    | DataType::Int { .. }
17999                    | DataType::SmallInt { .. }
18000                    | DataType::TinyInt { .. }
18001            ),
18002
18003            // Negation: -x returns integer if x is integer
18004            Expression::Neg(op) => self.returns_integer_type(&op.this),
18005
18006            // Parenthesized expression
18007            Expression::Paren(p) => self.returns_integer_type(&p.this),
18008
18009            // Column references and most expressions are assumed to need casting
18010            // since we don't have full type information
18011            _ => false,
18012        }
18013    }
18014
18015    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18016        self.write_keyword("DATEDIFF");
18017        self.write("(");
18018        if let Some(unit) = &f.unit {
18019            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18020            self.write(", ");
18021        }
18022        self.generate_expression(&f.this)?;
18023        self.write(", ");
18024        self.generate_expression(&f.expression)?;
18025        self.write(")");
18026        Ok(())
18027    }
18028
18029    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18030        self.write_keyword("DATE_TRUNC");
18031        self.write("('");
18032        self.write_datetime_field(&f.unit);
18033        self.write("', ");
18034        self.generate_expression(&f.this)?;
18035        self.write(")");
18036        Ok(())
18037    }
18038
18039    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18040        use crate::dialects::DialectType;
18041        use crate::expressions::DateTimeField;
18042
18043        self.write_keyword("LAST_DAY");
18044        self.write("(");
18045        self.generate_expression(&f.this)?;
18046        if let Some(unit) = &f.unit {
18047            self.write(", ");
18048            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18049            // WEEK(SUNDAY) -> WEEK
18050            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18051                if let DateTimeField::WeekWithModifier(_) = unit {
18052                    self.write_keyword("WEEK");
18053                } else {
18054                    self.write_datetime_field(unit);
18055                }
18056            } else {
18057                self.write_datetime_field(unit);
18058            }
18059        }
18060        self.write(")");
18061        Ok(())
18062    }
18063
18064    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18065        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18066        if matches!(
18067            self.config.dialect,
18068            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18069        ) {
18070            self.write_keyword("DATEPART");
18071            self.write("(");
18072            self.write_datetime_field(&f.field);
18073            self.write(", ");
18074            self.generate_expression(&f.this)?;
18075            self.write(")");
18076            return Ok(());
18077        }
18078        self.write_keyword("EXTRACT");
18079        self.write("(");
18080        // Hive/Spark use lowercase datetime fields in EXTRACT
18081        if matches!(
18082            self.config.dialect,
18083            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18084        ) {
18085            self.write_datetime_field_lower(&f.field);
18086        } else {
18087            self.write_datetime_field(&f.field);
18088        }
18089        self.write_space();
18090        self.write_keyword("FROM");
18091        self.write_space();
18092        self.generate_expression(&f.this)?;
18093        self.write(")");
18094        Ok(())
18095    }
18096
18097    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18098        self.write_keyword("TO_DATE");
18099        self.write("(");
18100        self.generate_expression(&f.this)?;
18101        if let Some(format) = &f.format {
18102            self.write(", ");
18103            self.generate_expression(format)?;
18104        }
18105        self.write(")");
18106        Ok(())
18107    }
18108
18109    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18110        self.write_keyword("TO_TIMESTAMP");
18111        self.write("(");
18112        self.generate_expression(&f.this)?;
18113        if let Some(format) = &f.format {
18114            self.write(", ");
18115            self.generate_expression(format)?;
18116        }
18117        self.write(")");
18118        Ok(())
18119    }
18120
18121    // Control flow function generators
18122
18123    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18124        use crate::dialects::DialectType;
18125
18126        // Generic mode: normalize IF to CASE WHEN
18127        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18128            self.write_keyword("CASE WHEN");
18129            self.write_space();
18130            self.generate_expression(&f.condition)?;
18131            self.write_space();
18132            self.write_keyword("THEN");
18133            self.write_space();
18134            self.generate_expression(&f.true_value)?;
18135            if let Some(false_val) = &f.false_value {
18136                self.write_space();
18137                self.write_keyword("ELSE");
18138                self.write_space();
18139                self.generate_expression(false_val)?;
18140            }
18141            self.write_space();
18142            self.write_keyword("END");
18143            return Ok(());
18144        }
18145
18146        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18147        if self.config.dialect == Some(DialectType::Exasol) {
18148            self.write_keyword("IF");
18149            self.write_space();
18150            self.generate_expression(&f.condition)?;
18151            self.write_space();
18152            self.write_keyword("THEN");
18153            self.write_space();
18154            self.generate_expression(&f.true_value)?;
18155            if let Some(false_val) = &f.false_value {
18156                self.write_space();
18157                self.write_keyword("ELSE");
18158                self.write_space();
18159                self.generate_expression(false_val)?;
18160            }
18161            self.write_space();
18162            self.write_keyword("ENDIF");
18163            return Ok(());
18164        }
18165
18166        // Choose function name based on target dialect
18167        let func_name = match self.config.dialect {
18168            Some(DialectType::Snowflake) => "IFF",
18169            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18170            Some(DialectType::Drill) => "`IF`",
18171            _ => "IF",
18172        };
18173        self.write(func_name);
18174        self.write("(");
18175        self.generate_expression(&f.condition)?;
18176        self.write(", ");
18177        self.generate_expression(&f.true_value)?;
18178        if let Some(false_val) = &f.false_value {
18179            self.write(", ");
18180            self.generate_expression(false_val)?;
18181        }
18182        self.write(")");
18183        Ok(())
18184    }
18185
18186    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18187        self.write_keyword("NVL2");
18188        self.write("(");
18189        self.generate_expression(&f.this)?;
18190        self.write(", ");
18191        self.generate_expression(&f.true_value)?;
18192        self.write(", ");
18193        self.generate_expression(&f.false_value)?;
18194        self.write(")");
18195        Ok(())
18196    }
18197
18198    // Typed aggregate function generators
18199
18200    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18201        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18202        let count_name = match self.config.normalize_functions {
18203            NormalizeFunctions::Upper => "COUNT".to_string(),
18204            NormalizeFunctions::Lower => "count".to_string(),
18205            NormalizeFunctions::None => f
18206                .original_name
18207                .clone()
18208                .unwrap_or_else(|| "COUNT".to_string()),
18209        };
18210        self.write(&count_name);
18211        self.write("(");
18212        if f.distinct {
18213            self.write_keyword("DISTINCT");
18214            self.write_space();
18215        }
18216        if f.star {
18217            self.write("*");
18218        } else if let Some(ref expr) = f.this {
18219            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18220            if let Expression::Tuple(tuple) = expr {
18221                // Check if we need to transform multi-arg COUNT DISTINCT
18222                // When dialect doesn't support multi_arg_distinct, transform:
18223                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18224                let needs_transform =
18225                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18226
18227                if needs_transform {
18228                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18229                    self.write_keyword("CASE");
18230                    for e in &tuple.expressions {
18231                        self.write_space();
18232                        self.write_keyword("WHEN");
18233                        self.write_space();
18234                        self.generate_expression(e)?;
18235                        self.write_space();
18236                        self.write_keyword("IS NULL THEN NULL");
18237                    }
18238                    self.write_space();
18239                    self.write_keyword("ELSE");
18240                    self.write(" (");
18241                    for (i, e) in tuple.expressions.iter().enumerate() {
18242                        if i > 0 {
18243                            self.write(", ");
18244                        }
18245                        self.generate_expression(e)?;
18246                    }
18247                    self.write(")");
18248                    self.write_space();
18249                    self.write_keyword("END");
18250                } else {
18251                    for (i, e) in tuple.expressions.iter().enumerate() {
18252                        if i > 0 {
18253                            self.write(", ");
18254                        }
18255                        self.generate_expression(e)?;
18256                    }
18257                }
18258            } else {
18259                self.generate_expression(expr)?;
18260            }
18261        }
18262        // RESPECT NULLS / IGNORE NULLS
18263        if let Some(ignore) = f.ignore_nulls {
18264            self.write_space();
18265            if ignore {
18266                self.write_keyword("IGNORE NULLS");
18267            } else {
18268                self.write_keyword("RESPECT NULLS");
18269            }
18270        }
18271        self.write(")");
18272        if let Some(ref filter) = f.filter {
18273            self.write_space();
18274            self.write_keyword("FILTER");
18275            self.write("(");
18276            self.write_keyword("WHERE");
18277            self.write_space();
18278            self.generate_expression(filter)?;
18279            self.write(")");
18280        }
18281        Ok(())
18282    }
18283
18284    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18285        // Apply function name normalization based on config
18286        let func_name = match self.config.normalize_functions {
18287            NormalizeFunctions::Upper => name.to_uppercase(),
18288            NormalizeFunctions::Lower => name.to_lowercase(),
18289            NormalizeFunctions::None => {
18290                // Use the original function name from parsing if available,
18291                // otherwise fall back to lowercase of the hardcoded constant
18292                if let Some(ref original) = f.name {
18293                    original.clone()
18294                } else {
18295                    name.to_lowercase()
18296                }
18297            }
18298        };
18299        self.write(&func_name);
18300        self.write("(");
18301        if f.distinct {
18302            self.write_keyword("DISTINCT");
18303            self.write_space();
18304        }
18305        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18306        if !matches!(f.this, Expression::Null(_)) {
18307            self.generate_expression(&f.this)?;
18308        }
18309        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18310        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18311        if self.config.ignore_nulls_in_func
18312            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18313        {
18314            match f.ignore_nulls {
18315                Some(true) => {
18316                    self.write_space();
18317                    self.write_keyword("IGNORE NULLS");
18318                }
18319                Some(false) => {
18320                    self.write_space();
18321                    self.write_keyword("RESPECT NULLS");
18322                }
18323                None => {}
18324            }
18325        }
18326        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18327        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18328        if let Some((ref expr, is_max)) = f.having_max {
18329            self.write_space();
18330            self.write_keyword("HAVING");
18331            self.write_space();
18332            if is_max {
18333                self.write_keyword("MAX");
18334            } else {
18335                self.write_keyword("MIN");
18336            }
18337            self.write_space();
18338            self.generate_expression(expr)?;
18339        }
18340        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18341        if !f.order_by.is_empty() {
18342            self.write_space();
18343            self.write_keyword("ORDER BY");
18344            self.write_space();
18345            for (i, ord) in f.order_by.iter().enumerate() {
18346                if i > 0 {
18347                    self.write(", ");
18348                }
18349                self.generate_ordered(ord)?;
18350            }
18351        }
18352        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18353        if let Some(ref limit) = f.limit {
18354            self.write_space();
18355            self.write_keyword("LIMIT");
18356            self.write_space();
18357            // Check if this is a Tuple representing LIMIT offset, count
18358            if let Expression::Tuple(t) = limit.as_ref() {
18359                if t.expressions.len() == 2 {
18360                    self.generate_expression(&t.expressions[0])?;
18361                    self.write(", ");
18362                    self.generate_expression(&t.expressions[1])?;
18363                } else {
18364                    self.generate_expression(limit)?;
18365                }
18366            } else {
18367                self.generate_expression(limit)?;
18368            }
18369        }
18370        self.write(")");
18371        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18372        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18373        if !self.config.ignore_nulls_in_func
18374            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18375        {
18376            match f.ignore_nulls {
18377                Some(true) => {
18378                    self.write_space();
18379                    self.write_keyword("IGNORE NULLS");
18380                }
18381                Some(false) => {
18382                    self.write_space();
18383                    self.write_keyword("RESPECT NULLS");
18384                }
18385                None => {}
18386            }
18387        }
18388        if let Some(ref filter) = f.filter {
18389            self.write_space();
18390            self.write_keyword("FILTER");
18391            self.write("(");
18392            self.write_keyword("WHERE");
18393            self.write_space();
18394            self.generate_expression(filter)?;
18395            self.write(")");
18396        }
18397        Ok(())
18398    }
18399
18400    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18401        self.write_keyword("GROUP_CONCAT");
18402        self.write("(");
18403        if f.distinct {
18404            self.write_keyword("DISTINCT");
18405            self.write_space();
18406        }
18407        self.generate_expression(&f.this)?;
18408        if let Some(ref order_by) = f.order_by {
18409            self.write_space();
18410            self.write_keyword("ORDER BY");
18411            self.write_space();
18412            for (i, ord) in order_by.iter().enumerate() {
18413                if i > 0 {
18414                    self.write(", ");
18415                }
18416                self.generate_ordered(ord)?;
18417            }
18418        }
18419        if let Some(ref sep) = f.separator {
18420            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18421            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18422            if matches!(
18423                self.config.dialect,
18424                Some(crate::dialects::DialectType::SQLite)
18425            ) {
18426                self.write(", ");
18427                self.generate_expression(sep)?;
18428            } else {
18429                self.write_space();
18430                self.write_keyword("SEPARATOR");
18431                self.write_space();
18432                self.generate_expression(sep)?;
18433            }
18434        }
18435        self.write(")");
18436        if let Some(ref filter) = f.filter {
18437            self.write_space();
18438            self.write_keyword("FILTER");
18439            self.write("(");
18440            self.write_keyword("WHERE");
18441            self.write_space();
18442            self.generate_expression(filter)?;
18443            self.write(")");
18444        }
18445        Ok(())
18446    }
18447
18448    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18449        let is_tsql = matches!(
18450            self.config.dialect,
18451            Some(crate::dialects::DialectType::TSQL)
18452        );
18453        self.write_keyword("STRING_AGG");
18454        self.write("(");
18455        if f.distinct {
18456            self.write_keyword("DISTINCT");
18457            self.write_space();
18458        }
18459        self.generate_expression(&f.this)?;
18460        if let Some(ref separator) = f.separator {
18461            self.write(", ");
18462            self.generate_expression(separator)?;
18463        }
18464        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18465        if !is_tsql {
18466            if let Some(ref order_by) = f.order_by {
18467                self.write_space();
18468                self.write_keyword("ORDER BY");
18469                self.write_space();
18470                for (i, ord) in order_by.iter().enumerate() {
18471                    if i > 0 {
18472                        self.write(", ");
18473                    }
18474                    self.generate_ordered(ord)?;
18475                }
18476            }
18477        }
18478        if let Some(ref limit) = f.limit {
18479            self.write_space();
18480            self.write_keyword("LIMIT");
18481            self.write_space();
18482            self.generate_expression(limit)?;
18483        }
18484        self.write(")");
18485        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18486        if is_tsql {
18487            if let Some(ref order_by) = f.order_by {
18488                self.write_space();
18489                self.write_keyword("WITHIN GROUP");
18490                self.write(" (");
18491                self.write_keyword("ORDER BY");
18492                self.write_space();
18493                for (i, ord) in order_by.iter().enumerate() {
18494                    if i > 0 {
18495                        self.write(", ");
18496                    }
18497                    self.generate_ordered(ord)?;
18498                }
18499                self.write(")");
18500            }
18501        }
18502        if let Some(ref filter) = f.filter {
18503            self.write_space();
18504            self.write_keyword("FILTER");
18505            self.write("(");
18506            self.write_keyword("WHERE");
18507            self.write_space();
18508            self.generate_expression(filter)?;
18509            self.write(")");
18510        }
18511        Ok(())
18512    }
18513
18514    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18515        use crate::dialects::DialectType;
18516        self.write_keyword("LISTAGG");
18517        self.write("(");
18518        if f.distinct {
18519            self.write_keyword("DISTINCT");
18520            self.write_space();
18521        }
18522        self.generate_expression(&f.this)?;
18523        if let Some(ref sep) = f.separator {
18524            self.write(", ");
18525            self.generate_expression(sep)?;
18526        } else if matches!(
18527            self.config.dialect,
18528            Some(DialectType::Trino) | Some(DialectType::Presto)
18529        ) {
18530            // Trino/Presto require explicit separator; default to ','
18531            self.write(", ','");
18532        }
18533        if let Some(ref overflow) = f.on_overflow {
18534            self.write_space();
18535            self.write_keyword("ON OVERFLOW");
18536            self.write_space();
18537            match overflow {
18538                ListAggOverflow::Error => self.write_keyword("ERROR"),
18539                ListAggOverflow::Truncate { filler, with_count } => {
18540                    self.write_keyword("TRUNCATE");
18541                    if let Some(ref fill) = filler {
18542                        self.write_space();
18543                        self.generate_expression(fill)?;
18544                    }
18545                    if *with_count {
18546                        self.write_space();
18547                        self.write_keyword("WITH COUNT");
18548                    } else {
18549                        self.write_space();
18550                        self.write_keyword("WITHOUT COUNT");
18551                    }
18552                }
18553            }
18554        }
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            for (i, ord) in order_by.iter().enumerate() {
18563                if i > 0 {
18564                    self.write(", ");
18565                }
18566                self.generate_ordered(ord)?;
18567            }
18568            self.write(")");
18569        }
18570        if let Some(ref filter) = f.filter {
18571            self.write_space();
18572            self.write_keyword("FILTER");
18573            self.write("(");
18574            self.write_keyword("WHERE");
18575            self.write_space();
18576            self.generate_expression(filter)?;
18577            self.write(")");
18578        }
18579        Ok(())
18580    }
18581
18582    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18583        self.write_keyword("SUM_IF");
18584        self.write("(");
18585        self.generate_expression(&f.this)?;
18586        self.write(", ");
18587        self.generate_expression(&f.condition)?;
18588        self.write(")");
18589        if let Some(ref filter) = f.filter {
18590            self.write_space();
18591            self.write_keyword("FILTER");
18592            self.write("(");
18593            self.write_keyword("WHERE");
18594            self.write_space();
18595            self.generate_expression(filter)?;
18596            self.write(")");
18597        }
18598        Ok(())
18599    }
18600
18601    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18602        self.write_keyword("APPROX_PERCENTILE");
18603        self.write("(");
18604        self.generate_expression(&f.this)?;
18605        self.write(", ");
18606        self.generate_expression(&f.percentile)?;
18607        if let Some(ref acc) = f.accuracy {
18608            self.write(", ");
18609            self.generate_expression(acc)?;
18610        }
18611        self.write(")");
18612        if let Some(ref filter) = f.filter {
18613            self.write_space();
18614            self.write_keyword("FILTER");
18615            self.write("(");
18616            self.write_keyword("WHERE");
18617            self.write_space();
18618            self.generate_expression(filter)?;
18619            self.write(")");
18620        }
18621        Ok(())
18622    }
18623
18624    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18625        self.write_keyword(name);
18626        self.write("(");
18627        self.generate_expression(&f.percentile)?;
18628        self.write(")");
18629        if let Some(ref order_by) = f.order_by {
18630            self.write_space();
18631            self.write_keyword("WITHIN GROUP");
18632            self.write(" (");
18633            self.write_keyword("ORDER BY");
18634            self.write_space();
18635            self.generate_expression(&f.this)?;
18636            for ord in order_by.iter() {
18637                if ord.desc {
18638                    self.write_space();
18639                    self.write_keyword("DESC");
18640                }
18641            }
18642            self.write(")");
18643        }
18644        if let Some(ref filter) = f.filter {
18645            self.write_space();
18646            self.write_keyword("FILTER");
18647            self.write("(");
18648            self.write_keyword("WHERE");
18649            self.write_space();
18650            self.generate_expression(filter)?;
18651            self.write(")");
18652        }
18653        Ok(())
18654    }
18655
18656    // Window function generators
18657
18658    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18659        self.write_keyword("NTILE");
18660        self.write("(");
18661        if let Some(num_buckets) = &f.num_buckets {
18662            self.generate_expression(num_buckets)?;
18663        }
18664        if let Some(order_by) = &f.order_by {
18665            self.write_keyword(" ORDER BY ");
18666            for (i, ob) in order_by.iter().enumerate() {
18667                if i > 0 {
18668                    self.write(", ");
18669                }
18670                self.generate_ordered(ob)?;
18671            }
18672        }
18673        self.write(")");
18674        Ok(())
18675    }
18676
18677    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18678        self.write_keyword(name);
18679        self.write("(");
18680        self.generate_expression(&f.this)?;
18681        if let Some(ref offset) = f.offset {
18682            self.write(", ");
18683            self.generate_expression(offset)?;
18684            if let Some(ref default) = f.default {
18685                self.write(", ");
18686                self.generate_expression(default)?;
18687            }
18688        }
18689        // IGNORE NULLS inside parens for dialects like BigQuery
18690        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18691            self.write_space();
18692            self.write_keyword("IGNORE NULLS");
18693        }
18694        self.write(")");
18695        // IGNORE NULLS outside parens for other dialects
18696        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18697            self.write_space();
18698            self.write_keyword("IGNORE NULLS");
18699        }
18700        Ok(())
18701    }
18702
18703    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18704        self.write_keyword(name);
18705        self.write("(");
18706        self.generate_expression(&f.this)?;
18707        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18708        if self.config.ignore_nulls_in_func {
18709            match f.ignore_nulls {
18710                Some(true) => {
18711                    self.write_space();
18712                    self.write_keyword("IGNORE NULLS");
18713                }
18714                Some(false) => {
18715                    self.write_space();
18716                    self.write_keyword("RESPECT NULLS");
18717                }
18718                None => {}
18719            }
18720        }
18721        self.write(")");
18722        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18723        if !self.config.ignore_nulls_in_func {
18724            match f.ignore_nulls {
18725                Some(true) => {
18726                    self.write_space();
18727                    self.write_keyword("IGNORE NULLS");
18728                }
18729                Some(false) => {
18730                    self.write_space();
18731                    self.write_keyword("RESPECT NULLS");
18732                }
18733                None => {}
18734            }
18735        }
18736        Ok(())
18737    }
18738
18739    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18740        self.write_keyword("NTH_VALUE");
18741        self.write("(");
18742        self.generate_expression(&f.this)?;
18743        self.write(", ");
18744        self.generate_expression(&f.offset)?;
18745        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18746        if self.config.ignore_nulls_in_func {
18747            match f.ignore_nulls {
18748                Some(true) => {
18749                    self.write_space();
18750                    self.write_keyword("IGNORE NULLS");
18751                }
18752                Some(false) => {
18753                    self.write_space();
18754                    self.write_keyword("RESPECT NULLS");
18755                }
18756                None => {}
18757            }
18758        }
18759        self.write(")");
18760        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18761        if matches!(
18762            self.config.dialect,
18763            Some(crate::dialects::DialectType::Snowflake)
18764        ) {
18765            match f.from_first {
18766                Some(true) => {
18767                    self.write_space();
18768                    self.write_keyword("FROM FIRST");
18769                }
18770                Some(false) => {
18771                    self.write_space();
18772                    self.write_keyword("FROM LAST");
18773                }
18774                None => {}
18775            }
18776        }
18777        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18778        if !self.config.ignore_nulls_in_func {
18779            match f.ignore_nulls {
18780                Some(true) => {
18781                    self.write_space();
18782                    self.write_keyword("IGNORE NULLS");
18783                }
18784                Some(false) => {
18785                    self.write_space();
18786                    self.write_keyword("RESPECT NULLS");
18787                }
18788                None => {}
18789            }
18790        }
18791        Ok(())
18792    }
18793
18794    // Additional string function generators
18795
18796    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18797        // Standard syntax: POSITION(substr IN str)
18798        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18799        if matches!(
18800            self.config.dialect,
18801            Some(crate::dialects::DialectType::ClickHouse)
18802        ) {
18803            self.write_keyword("POSITION");
18804            self.write("(");
18805            self.generate_expression(&f.string)?;
18806            self.write(", ");
18807            self.generate_expression(&f.substring)?;
18808            if let Some(ref start) = f.start {
18809                self.write(", ");
18810                self.generate_expression(start)?;
18811            }
18812            self.write(")");
18813            return Ok(());
18814        }
18815
18816        self.write_keyword("POSITION");
18817        self.write("(");
18818        self.generate_expression(&f.substring)?;
18819        self.write_space();
18820        self.write_keyword("IN");
18821        self.write_space();
18822        self.generate_expression(&f.string)?;
18823        if let Some(ref start) = f.start {
18824            self.write(", ");
18825            self.generate_expression(start)?;
18826        }
18827        self.write(")");
18828        Ok(())
18829    }
18830
18831    // Additional math function generators
18832
18833    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18834        // Teradata RANDOM(lower, upper)
18835        if f.lower.is_some() || f.upper.is_some() {
18836            self.write_keyword("RANDOM");
18837            self.write("(");
18838            if let Some(ref lower) = f.lower {
18839                self.generate_expression(lower)?;
18840            }
18841            if let Some(ref upper) = f.upper {
18842                self.write(", ");
18843                self.generate_expression(upper)?;
18844            }
18845            self.write(")");
18846            return Ok(());
18847        }
18848        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18849        let func_name = match self.config.dialect {
18850            Some(crate::dialects::DialectType::Snowflake)
18851            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18852            _ => "RAND",
18853        };
18854        self.write_keyword(func_name);
18855        self.write("(");
18856        // DuckDB doesn't support seeded RANDOM, so skip the seed
18857        if !matches!(
18858            self.config.dialect,
18859            Some(crate::dialects::DialectType::DuckDB)
18860        ) {
18861            if let Some(ref seed) = f.seed {
18862                self.generate_expression(seed)?;
18863            }
18864        }
18865        self.write(")");
18866        Ok(())
18867    }
18868
18869    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18870        self.write_keyword("TRUNCATE");
18871        self.write("(");
18872        self.generate_expression(&f.this)?;
18873        if let Some(ref decimals) = f.decimals {
18874            self.write(", ");
18875            self.generate_expression(decimals)?;
18876        }
18877        self.write(")");
18878        Ok(())
18879    }
18880
18881    // Control flow generators
18882
18883    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18884        self.write_keyword("DECODE");
18885        self.write("(");
18886        self.generate_expression(&f.this)?;
18887        for (search, result) in &f.search_results {
18888            self.write(", ");
18889            self.generate_expression(search)?;
18890            self.write(", ");
18891            self.generate_expression(result)?;
18892        }
18893        if let Some(ref default) = f.default {
18894            self.write(", ");
18895            self.generate_expression(default)?;
18896        }
18897        self.write(")");
18898        Ok(())
18899    }
18900
18901    // Date/time function generators
18902
18903    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18904        self.write_keyword(name);
18905        self.write("(");
18906        self.generate_expression(&f.this)?;
18907        self.write(", ");
18908        self.generate_expression(&f.format)?;
18909        self.write(")");
18910        Ok(())
18911    }
18912
18913    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18914        self.write_keyword("FROM_UNIXTIME");
18915        self.write("(");
18916        self.generate_expression(&f.this)?;
18917        if let Some(ref format) = f.format {
18918            self.write(", ");
18919            self.generate_expression(format)?;
18920        }
18921        self.write(")");
18922        Ok(())
18923    }
18924
18925    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18926        self.write_keyword("UNIX_TIMESTAMP");
18927        self.write("(");
18928        if let Some(ref expr) = f.this {
18929            self.generate_expression(expr)?;
18930            if let Some(ref format) = f.format {
18931                self.write(", ");
18932                self.generate_expression(format)?;
18933            }
18934        } else if matches!(
18935            self.config.dialect,
18936            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18937        ) {
18938            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18939            self.write_keyword("CURRENT_TIMESTAMP");
18940            self.write("()");
18941        }
18942        self.write(")");
18943        Ok(())
18944    }
18945
18946    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18947        self.write_keyword("MAKE_DATE");
18948        self.write("(");
18949        self.generate_expression(&f.year)?;
18950        self.write(", ");
18951        self.generate_expression(&f.month)?;
18952        self.write(", ");
18953        self.generate_expression(&f.day)?;
18954        self.write(")");
18955        Ok(())
18956    }
18957
18958    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18959        self.write_keyword("MAKE_TIMESTAMP");
18960        self.write("(");
18961        self.generate_expression(&f.year)?;
18962        self.write(", ");
18963        self.generate_expression(&f.month)?;
18964        self.write(", ");
18965        self.generate_expression(&f.day)?;
18966        self.write(", ");
18967        self.generate_expression(&f.hour)?;
18968        self.write(", ");
18969        self.generate_expression(&f.minute)?;
18970        self.write(", ");
18971        self.generate_expression(&f.second)?;
18972        if let Some(ref tz) = f.timezone {
18973            self.write(", ");
18974            self.generate_expression(tz)?;
18975        }
18976        self.write(")");
18977        Ok(())
18978    }
18979
18980    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18981    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18982        match expr {
18983            Expression::Struct(s) => {
18984                if s.fields.iter().all(|(name, _)| name.is_some()) {
18985                    Some(
18986                        s.fields
18987                            .iter()
18988                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18989                            .collect(),
18990                    )
18991                } else {
18992                    None
18993                }
18994            }
18995            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18996                // Check if all args are Alias (named fields)
18997                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
18998                    Some(
18999                        f.args
19000                            .iter()
19001                            .filter_map(|a| {
19002                                if let Expression::Alias(alias) = a {
19003                                    Some(alias.alias.name.clone())
19004                                } else {
19005                                    None
19006                                }
19007                            })
19008                            .collect(),
19009                    )
19010                } else {
19011                    None
19012                }
19013            }
19014            _ => None,
19015        }
19016    }
19017
19018    /// Check if a struct expression has any unnamed fields
19019    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
19020        match expr {
19021            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
19022            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19023                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
19024            }
19025            _ => false,
19026        }
19027    }
19028
19029    /// Get the field count of a struct expression
19030    fn struct_field_count(expr: &Expression) -> usize {
19031        match expr {
19032            Expression::Struct(s) => s.fields.len(),
19033            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
19034            _ => 0,
19035        }
19036    }
19037
19038    /// Apply field names to an unnamed struct expression, producing a new expression with names
19039    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
19040        match expr {
19041            Expression::Struct(s) => {
19042                let mut new_fields = Vec::with_capacity(s.fields.len());
19043                for (i, (name, value)) in s.fields.iter().enumerate() {
19044                    if name.is_none() && i < field_names.len() {
19045                        new_fields.push((Some(field_names[i].clone()), value.clone()));
19046                    } else {
19047                        new_fields.push((name.clone(), value.clone()));
19048                    }
19049                }
19050                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
19051            }
19052            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19053                let mut new_args = Vec::with_capacity(f.args.len());
19054                for (i, arg) in f.args.iter().enumerate() {
19055                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
19056                        // Wrap the value in an Alias with the inherited name
19057                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19058                            this: arg.clone(),
19059                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19060                            column_aliases: Vec::new(),
19061                            pre_alias_comments: Vec::new(),
19062                            trailing_comments: Vec::new(),
19063                        })));
19064                    } else {
19065                        new_args.push(arg.clone());
19066                    }
19067                }
19068                Expression::Function(Box::new(crate::expressions::Function {
19069                    name: f.name.clone(),
19070                    args: new_args,
19071                    distinct: f.distinct,
19072                    trailing_comments: f.trailing_comments.clone(),
19073                    use_bracket_syntax: f.use_bracket_syntax,
19074                    no_parens: f.no_parens,
19075                    quoted: f.quoted,
19076                    span: None,
19077                }))
19078            }
19079            _ => expr.clone(),
19080        }
19081    }
19082
19083    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19084    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19085    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19086    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19087        let first = match expressions.first() {
19088            Some(e) => e,
19089            None => return expressions.to_vec(),
19090        };
19091
19092        let field_names = match Self::extract_struct_field_names(first) {
19093            Some(names) if !names.is_empty() => names,
19094            _ => return expressions.to_vec(),
19095        };
19096
19097        let mut result = Vec::with_capacity(expressions.len());
19098        for (idx, expr) in expressions.iter().enumerate() {
19099            if idx == 0 {
19100                result.push(expr.clone());
19101                continue;
19102            }
19103            // Check if this is a struct with unnamed fields that needs name propagation
19104            if Self::struct_field_count(expr) == field_names.len()
19105                && Self::struct_has_unnamed_fields(expr)
19106            {
19107                result.push(Self::apply_struct_field_names(expr, &field_names));
19108            } else {
19109                result.push(expr.clone());
19110            }
19111        }
19112        result
19113    }
19114
19115    // Array function generators
19116
19117    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19118        // Apply struct name inheritance for target dialects that need it
19119        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19120        let needs_inheritance = matches!(
19121            self.config.dialect,
19122            Some(DialectType::DuckDB)
19123                | Some(DialectType::Spark)
19124                | Some(DialectType::Databricks)
19125                | Some(DialectType::Hive)
19126                | Some(DialectType::Snowflake)
19127                | Some(DialectType::Presto)
19128                | Some(DialectType::Trino)
19129        );
19130        let propagated: Vec<Expression>;
19131        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19132            propagated = Self::inherit_struct_field_names(&f.expressions);
19133            &propagated
19134        } else {
19135            &f.expressions
19136        };
19137
19138        // Check if elements should be split onto multiple lines (pretty + too wide)
19139        let should_split = if self.config.pretty && !expressions.is_empty() {
19140            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19141            for expr in expressions {
19142                let mut temp_gen = Generator::with_config(self.config.clone());
19143                temp_gen.config.pretty = false;
19144                temp_gen.generate_expression(expr)?;
19145                expr_strings.push(temp_gen.output);
19146            }
19147            self.too_wide(&expr_strings)
19148        } else {
19149            false
19150        };
19151
19152        if f.bracket_notation {
19153            // For Spark/Databricks, use ARRAY(...) with parens
19154            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19155            // For others (DuckDB, Snowflake), use bare [...]
19156            let (open, close) = match self.config.dialect {
19157                None
19158                | Some(DialectType::Generic)
19159                | Some(DialectType::Spark)
19160                | Some(DialectType::Databricks)
19161                | Some(DialectType::Hive) => {
19162                    self.write_keyword("ARRAY");
19163                    ("(", ")")
19164                }
19165                Some(DialectType::Presto)
19166                | Some(DialectType::Trino)
19167                | Some(DialectType::PostgreSQL)
19168                | Some(DialectType::Redshift)
19169                | Some(DialectType::Materialize)
19170                | Some(DialectType::RisingWave)
19171                | Some(DialectType::CockroachDB) => {
19172                    self.write_keyword("ARRAY");
19173                    ("[", "]")
19174                }
19175                _ => ("[", "]"),
19176            };
19177            self.write(open);
19178            if should_split {
19179                self.write_newline();
19180                self.indent_level += 1;
19181                for (i, expr) in expressions.iter().enumerate() {
19182                    self.write_indent();
19183                    self.generate_expression(expr)?;
19184                    if i + 1 < expressions.len() {
19185                        self.write(",");
19186                    }
19187                    self.write_newline();
19188                }
19189                self.indent_level -= 1;
19190                self.write_indent();
19191            } else {
19192                for (i, expr) in expressions.iter().enumerate() {
19193                    if i > 0 {
19194                        self.write(", ");
19195                    }
19196                    self.generate_expression(expr)?;
19197                }
19198            }
19199            self.write(close);
19200        } else {
19201            // Use LIST keyword if that was the original syntax (DuckDB)
19202            if f.use_list_keyword {
19203                self.write_keyword("LIST");
19204            } else {
19205                self.write_keyword("ARRAY");
19206            }
19207            // For Spark/Hive, always use ARRAY(...) with parens
19208            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19209            let has_subquery = expressions
19210                .iter()
19211                .any(|e| matches!(e, Expression::Select(_)));
19212            let (open, close) = if matches!(
19213                self.config.dialect,
19214                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19215            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19216                && has_subquery)
19217            {
19218                ("(", ")")
19219            } else {
19220                ("[", "]")
19221            };
19222            self.write(open);
19223            if should_split {
19224                self.write_newline();
19225                self.indent_level += 1;
19226                for (i, expr) in expressions.iter().enumerate() {
19227                    self.write_indent();
19228                    self.generate_expression(expr)?;
19229                    if i + 1 < expressions.len() {
19230                        self.write(",");
19231                    }
19232                    self.write_newline();
19233                }
19234                self.indent_level -= 1;
19235                self.write_indent();
19236            } else {
19237                for (i, expr) in expressions.iter().enumerate() {
19238                    if i > 0 {
19239                        self.write(", ");
19240                    }
19241                    self.generate_expression(expr)?;
19242                }
19243            }
19244            self.write(close);
19245        }
19246        Ok(())
19247    }
19248
19249    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19250        self.write_keyword("ARRAY_SORT");
19251        self.write("(");
19252        self.generate_expression(&f.this)?;
19253        if let Some(ref comp) = f.comparator {
19254            self.write(", ");
19255            self.generate_expression(comp)?;
19256        }
19257        self.write(")");
19258        Ok(())
19259    }
19260
19261    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19262        self.write_keyword(name);
19263        self.write("(");
19264        self.generate_expression(&f.this)?;
19265        self.write(", ");
19266        self.generate_expression(&f.separator)?;
19267        if let Some(ref null_rep) = f.null_replacement {
19268            self.write(", ");
19269            self.generate_expression(null_rep)?;
19270        }
19271        self.write(")");
19272        Ok(())
19273    }
19274
19275    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19276        self.write_keyword("UNNEST");
19277        self.write("(");
19278        self.generate_expression(&f.this)?;
19279        for extra in &f.expressions {
19280            self.write(", ");
19281            self.generate_expression(extra)?;
19282        }
19283        self.write(")");
19284        if f.with_ordinality {
19285            self.write_space();
19286            if self.config.unnest_with_ordinality {
19287                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19288                self.write_keyword("WITH ORDINALITY");
19289            } else if f.offset_alias.is_some() {
19290                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19291                // Alias (if any) comes BEFORE WITH OFFSET
19292                if let Some(ref alias) = f.alias {
19293                    self.write_keyword("AS");
19294                    self.write_space();
19295                    self.generate_identifier(alias)?;
19296                    self.write_space();
19297                }
19298                self.write_keyword("WITH OFFSET");
19299                if let Some(ref offset_alias) = f.offset_alias {
19300                    self.write_space();
19301                    self.write_keyword("AS");
19302                    self.write_space();
19303                    self.generate_identifier(offset_alias)?;
19304                }
19305            } else {
19306                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19307                self.write_keyword("WITH OFFSET");
19308                if f.alias.is_none() {
19309                    self.write(" AS offset");
19310                }
19311            }
19312        }
19313        if let Some(ref alias) = f.alias {
19314            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19315            let should_add_alias = if !f.with_ordinality {
19316                true
19317            } else if self.config.unnest_with_ordinality {
19318                // Presto/Trino: alias comes after WITH ORDINALITY
19319                true
19320            } else if f.offset_alias.is_some() {
19321                // BigQuery expansion: alias already handled above
19322                false
19323            } else {
19324                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19325                true
19326            };
19327            if should_add_alias {
19328                self.write_space();
19329                self.write_keyword("AS");
19330                self.write_space();
19331                self.generate_identifier(alias)?;
19332            }
19333        }
19334        Ok(())
19335    }
19336
19337    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19338        self.write_keyword("FILTER");
19339        self.write("(");
19340        self.generate_expression(&f.this)?;
19341        self.write(", ");
19342        self.generate_expression(&f.filter)?;
19343        self.write(")");
19344        Ok(())
19345    }
19346
19347    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19348        self.write_keyword("TRANSFORM");
19349        self.write("(");
19350        self.generate_expression(&f.this)?;
19351        self.write(", ");
19352        self.generate_expression(&f.transform)?;
19353        self.write(")");
19354        Ok(())
19355    }
19356
19357    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19358        self.write_keyword(name);
19359        self.write("(");
19360        self.generate_expression(&f.start)?;
19361        self.write(", ");
19362        self.generate_expression(&f.stop)?;
19363        if let Some(ref step) = f.step {
19364            self.write(", ");
19365            self.generate_expression(step)?;
19366        }
19367        self.write(")");
19368        Ok(())
19369    }
19370
19371    // Struct function generators
19372
19373    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19374        self.write_keyword("STRUCT");
19375        self.write("(");
19376        for (i, (name, expr)) in f.fields.iter().enumerate() {
19377            if i > 0 {
19378                self.write(", ");
19379            }
19380            if let Some(ref id) = name {
19381                self.generate_identifier(id)?;
19382                self.write(" ");
19383                self.write_keyword("AS");
19384                self.write(" ");
19385            }
19386            self.generate_expression(expr)?;
19387        }
19388        self.write(")");
19389        Ok(())
19390    }
19391
19392    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19393    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19394        // Extract named/unnamed fields from function args
19395        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19396        let mut names: Vec<Option<String>> = Vec::new();
19397        let mut values: Vec<&Expression> = Vec::new();
19398        let mut all_named = true;
19399
19400        for arg in &func.args {
19401            match arg {
19402                Expression::Alias(a) => {
19403                    names.push(Some(a.alias.name.clone()));
19404                    values.push(&a.this);
19405                }
19406                _ => {
19407                    names.push(None);
19408                    values.push(arg);
19409                    all_named = false;
19410                }
19411            }
19412        }
19413
19414        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19415            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19416            self.write("{");
19417            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19418                if i > 0 {
19419                    self.write(", ");
19420                }
19421                if let Some(n) = name {
19422                    self.write("'");
19423                    self.write(n);
19424                    self.write("'");
19425                } else {
19426                    self.write("'_");
19427                    self.write(&i.to_string());
19428                    self.write("'");
19429                }
19430                self.write(": ");
19431                self.generate_expression(value)?;
19432            }
19433            self.write("}");
19434            return Ok(());
19435        }
19436
19437        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19438            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19439            self.write_keyword("OBJECT_CONSTRUCT");
19440            self.write("(");
19441            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19442                if i > 0 {
19443                    self.write(", ");
19444                }
19445                if let Some(n) = name {
19446                    self.write("'");
19447                    self.write(n);
19448                    self.write("'");
19449                } else {
19450                    self.write("'_");
19451                    self.write(&i.to_string());
19452                    self.write("'");
19453                }
19454                self.write(", ");
19455                self.generate_expression(value)?;
19456            }
19457            self.write(")");
19458            return Ok(());
19459        }
19460
19461        if matches!(
19462            self.config.dialect,
19463            Some(DialectType::Presto) | Some(DialectType::Trino)
19464        ) {
19465            if all_named && !names.is_empty() {
19466                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19467                // Need to infer types from values
19468                self.write_keyword("CAST");
19469                self.write("(");
19470                self.write_keyword("ROW");
19471                self.write("(");
19472                for (i, value) in values.iter().enumerate() {
19473                    if i > 0 {
19474                        self.write(", ");
19475                    }
19476                    self.generate_expression(value)?;
19477                }
19478                self.write(")");
19479                self.write(" ");
19480                self.write_keyword("AS");
19481                self.write(" ");
19482                self.write_keyword("ROW");
19483                self.write("(");
19484                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19485                    if i > 0 {
19486                        self.write(", ");
19487                    }
19488                    if let Some(n) = name {
19489                        self.write(n);
19490                    }
19491                    self.write(" ");
19492                    let type_str = Self::infer_sql_type_for_presto(value);
19493                    self.write_keyword(&type_str);
19494                }
19495                self.write(")");
19496                self.write(")");
19497            } else {
19498                // Unnamed: ROW(values...)
19499                self.write_keyword("ROW");
19500                self.write("(");
19501                for (i, value) in values.iter().enumerate() {
19502                    if i > 0 {
19503                        self.write(", ");
19504                    }
19505                    self.generate_expression(value)?;
19506                }
19507                self.write(")");
19508            }
19509            return Ok(());
19510        }
19511
19512        // Default: ROW(values...) for other dialects
19513        self.write_keyword("ROW");
19514        self.write("(");
19515        for (i, value) in values.iter().enumerate() {
19516            if i > 0 {
19517                self.write(", ");
19518            }
19519            self.generate_expression(value)?;
19520        }
19521        self.write(")");
19522        Ok(())
19523    }
19524
19525    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19526    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19527        match expr {
19528            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19529            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19530                if n.contains('.') {
19531                    "DOUBLE".to_string()
19532                } else {
19533                    "INTEGER".to_string()
19534                }
19535            }
19536            Expression::Boolean(_) => "BOOLEAN".to_string(),
19537            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19538            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19539                "TIMESTAMP".to_string()
19540            }
19541            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19542                "TIMESTAMP".to_string()
19543            }
19544            Expression::Array(_) | Expression::ArrayFunc(_) => {
19545                // Try to infer element type from first element
19546                "ARRAY(VARCHAR)".to_string()
19547            }
19548            // For nested structs - generate a nested ROW type by inspecting fields
19549            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19550            Expression::Function(f) => {
19551                let up = f.name.to_uppercase();
19552                if up == "STRUCT" {
19553                    "ROW".to_string()
19554                } else if up == "CURRENT_DATE" {
19555                    "DATE".to_string()
19556                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19557                    "TIMESTAMP".to_string()
19558                } else {
19559                    "VARCHAR".to_string()
19560                }
19561            }
19562            _ => "VARCHAR".to_string(),
19563        }
19564    }
19565
19566    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19567        // DuckDB uses STRUCT_EXTRACT function syntax
19568        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19569            self.write_keyword("STRUCT_EXTRACT");
19570            self.write("(");
19571            self.generate_expression(&f.this)?;
19572            self.write(", ");
19573            // Output field name as string literal
19574            self.write("'");
19575            self.write(&f.field.name);
19576            self.write("'");
19577            self.write(")");
19578            return Ok(());
19579        }
19580        self.generate_expression(&f.this)?;
19581        self.write(".");
19582        self.generate_identifier(&f.field)
19583    }
19584
19585    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19586        self.write_keyword("NAMED_STRUCT");
19587        self.write("(");
19588        for (i, (name, value)) in f.pairs.iter().enumerate() {
19589            if i > 0 {
19590                self.write(", ");
19591            }
19592            self.generate_expression(name)?;
19593            self.write(", ");
19594            self.generate_expression(value)?;
19595        }
19596        self.write(")");
19597        Ok(())
19598    }
19599
19600    // Map function generators
19601
19602    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19603        if f.curly_brace_syntax {
19604            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19605            if f.with_map_keyword {
19606                self.write_keyword("MAP");
19607                self.write(" ");
19608            }
19609            self.write("{");
19610            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19611                if i > 0 {
19612                    self.write(", ");
19613                }
19614                self.generate_expression(key)?;
19615                self.write(": ");
19616                self.generate_expression(val)?;
19617            }
19618            self.write("}");
19619        } else {
19620            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19621            self.write_keyword("MAP");
19622            self.write("(");
19623            self.write_keyword("ARRAY");
19624            self.write("[");
19625            for (i, key) in f.keys.iter().enumerate() {
19626                if i > 0 {
19627                    self.write(", ");
19628                }
19629                self.generate_expression(key)?;
19630            }
19631            self.write("], ");
19632            self.write_keyword("ARRAY");
19633            self.write("[");
19634            for (i, val) in f.values.iter().enumerate() {
19635                if i > 0 {
19636                    self.write(", ");
19637                }
19638                self.generate_expression(val)?;
19639            }
19640            self.write("])");
19641        }
19642        Ok(())
19643    }
19644
19645    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19646        self.write_keyword(name);
19647        self.write("(");
19648        self.generate_expression(&f.this)?;
19649        self.write(", ");
19650        self.generate_expression(&f.transform)?;
19651        self.write(")");
19652        Ok(())
19653    }
19654
19655    // JSON function generators
19656
19657    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19658        use crate::dialects::DialectType;
19659
19660        // Check if we should use arrow syntax (-> or ->>)
19661        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19662
19663        if use_arrow {
19664            // Output arrow syntax: expr -> path or expr ->> path
19665            self.generate_expression(&f.this)?;
19666            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19667                self.write(" ->> ");
19668            } else {
19669                self.write(" -> ");
19670            }
19671            self.generate_expression(&f.path)?;
19672            return Ok(());
19673        }
19674
19675        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19676        if f.hash_arrow_syntax
19677            && matches!(
19678                self.config.dialect,
19679                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19680            )
19681        {
19682            self.generate_expression(&f.this)?;
19683            self.write(" #>> ");
19684            self.generate_expression(&f.path)?;
19685            return Ok(());
19686        }
19687
19688        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19689        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19690        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19691            match name {
19692                "JSON_EXTRACT_SCALAR"
19693                | "JSON_EXTRACT_PATH_TEXT"
19694                | "JSON_EXTRACT"
19695                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19696                _ => name,
19697            }
19698        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19699            match name {
19700                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19701                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19702                _ => name,
19703            }
19704        } else {
19705            name
19706        };
19707
19708        self.write_keyword(func_name);
19709        self.write("(");
19710        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19711        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19712            if let Expression::Cast(ref cast) = f.this {
19713                if matches!(cast.to, crate::expressions::DataType::Json) {
19714                    self.generate_expression(&cast.this)?;
19715                } else {
19716                    self.generate_expression(&f.this)?;
19717                }
19718            } else {
19719                self.generate_expression(&f.this)?;
19720            }
19721        } else {
19722            self.generate_expression(&f.this)?;
19723        }
19724        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19725        // decompose JSON path into separate string arguments
19726        if matches!(
19727            self.config.dialect,
19728            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19729        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19730        {
19731            if let Expression::Literal(Literal::String(ref s)) = f.path {
19732                let parts = Self::decompose_json_path(s);
19733                for part in &parts {
19734                    self.write(", '");
19735                    self.write(part);
19736                    self.write("'");
19737                }
19738            } else {
19739                self.write(", ");
19740                self.generate_expression(&f.path)?;
19741            }
19742        } else {
19743            self.write(", ");
19744            self.generate_expression(&f.path)?;
19745        }
19746
19747        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19748        // These go BEFORE the closing parenthesis
19749        if let Some(ref wrapper) = f.wrapper_option {
19750            self.write_space();
19751            self.write_keyword(wrapper);
19752        }
19753        if let Some(ref quotes) = f.quotes_option {
19754            self.write_space();
19755            self.write_keyword(quotes);
19756            if f.on_scalar_string {
19757                self.write_space();
19758                self.write_keyword("ON SCALAR STRING");
19759            }
19760        }
19761        if let Some(ref on_err) = f.on_error {
19762            self.write_space();
19763            self.write_keyword(on_err);
19764        }
19765        if let Some(ref ret_type) = f.returning {
19766            self.write_space();
19767            self.write_keyword("RETURNING");
19768            self.write_space();
19769            self.generate_data_type(ret_type)?;
19770        }
19771
19772        self.write(")");
19773        Ok(())
19774    }
19775
19776    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19777    fn dialect_supports_json_arrow(&self) -> bool {
19778        use crate::dialects::DialectType;
19779        match self.config.dialect {
19780            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19781            Some(DialectType::PostgreSQL) => true,
19782            Some(DialectType::MySQL) => true,
19783            Some(DialectType::DuckDB) => true,
19784            Some(DialectType::CockroachDB) => true,
19785            Some(DialectType::StarRocks) => true,
19786            Some(DialectType::SQLite) => true,
19787            // Other dialects use function syntax
19788            _ => false,
19789        }
19790    }
19791
19792    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19793        use crate::dialects::DialectType;
19794
19795        // PostgreSQL uses #> operator for JSONB path extraction
19796        if matches!(
19797            self.config.dialect,
19798            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19799        ) && name == "JSON_EXTRACT_PATH"
19800        {
19801            self.generate_expression(&f.this)?;
19802            self.write(" #> ");
19803            if f.paths.len() == 1 {
19804                self.generate_expression(&f.paths[0])?;
19805            } else {
19806                // Multiple paths: ARRAY[path1, path2, ...]
19807                self.write_keyword("ARRAY");
19808                self.write("[");
19809                for (i, path) in f.paths.iter().enumerate() {
19810                    if i > 0 {
19811                        self.write(", ");
19812                    }
19813                    self.generate_expression(path)?;
19814                }
19815                self.write("]");
19816            }
19817            return Ok(());
19818        }
19819
19820        self.write_keyword(name);
19821        self.write("(");
19822        self.generate_expression(&f.this)?;
19823        for path in &f.paths {
19824            self.write(", ");
19825            self.generate_expression(path)?;
19826        }
19827        self.write(")");
19828        Ok(())
19829    }
19830
19831    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19832        use crate::dialects::DialectType;
19833
19834        self.write_keyword("JSON_OBJECT");
19835        self.write("(");
19836        if f.star {
19837            self.write("*");
19838        } else {
19839            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19840            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19841            // Also respect the json_key_value_pair_sep config
19842            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19843                || matches!(
19844                    self.config.dialect,
19845                    Some(DialectType::BigQuery)
19846                        | Some(DialectType::MySQL)
19847                        | Some(DialectType::SQLite)
19848                );
19849
19850            for (i, (key, value)) in f.pairs.iter().enumerate() {
19851                if i > 0 {
19852                    self.write(", ");
19853                }
19854                self.generate_expression(key)?;
19855                if use_comma_syntax {
19856                    self.write(", ");
19857                } else {
19858                    self.write(": ");
19859                }
19860                self.generate_expression(value)?;
19861            }
19862        }
19863        if let Some(null_handling) = f.null_handling {
19864            self.write_space();
19865            match null_handling {
19866                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19867                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19868            }
19869        }
19870        if f.with_unique_keys {
19871            self.write_space();
19872            self.write_keyword("WITH UNIQUE KEYS");
19873        }
19874        if let Some(ref ret_type) = f.returning_type {
19875            self.write_space();
19876            self.write_keyword("RETURNING");
19877            self.write_space();
19878            self.generate_data_type(ret_type)?;
19879            if f.format_json {
19880                self.write_space();
19881                self.write_keyword("FORMAT JSON");
19882            }
19883            if let Some(ref enc) = f.encoding {
19884                self.write_space();
19885                self.write_keyword("ENCODING");
19886                self.write_space();
19887                self.write(enc);
19888            }
19889        }
19890        self.write(")");
19891        Ok(())
19892    }
19893
19894    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19895        self.write_keyword(name);
19896        self.write("(");
19897        self.generate_expression(&f.this)?;
19898        for (path, value) in &f.path_values {
19899            self.write(", ");
19900            self.generate_expression(path)?;
19901            self.write(", ");
19902            self.generate_expression(value)?;
19903        }
19904        self.write(")");
19905        Ok(())
19906    }
19907
19908    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19909        self.write_keyword("JSON_ARRAYAGG");
19910        self.write("(");
19911        self.generate_expression(&f.this)?;
19912        if let Some(ref order_by) = f.order_by {
19913            self.write_space();
19914            self.write_keyword("ORDER BY");
19915            self.write_space();
19916            for (i, ord) in order_by.iter().enumerate() {
19917                if i > 0 {
19918                    self.write(", ");
19919                }
19920                self.generate_ordered(ord)?;
19921            }
19922        }
19923        if let Some(null_handling) = f.null_handling {
19924            self.write_space();
19925            match null_handling {
19926                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19927                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19928            }
19929        }
19930        self.write(")");
19931        if let Some(ref filter) = f.filter {
19932            self.write_space();
19933            self.write_keyword("FILTER");
19934            self.write("(");
19935            self.write_keyword("WHERE");
19936            self.write_space();
19937            self.generate_expression(filter)?;
19938            self.write(")");
19939        }
19940        Ok(())
19941    }
19942
19943    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19944        self.write_keyword("JSON_OBJECTAGG");
19945        self.write("(");
19946        self.generate_expression(&f.key)?;
19947        self.write(": ");
19948        self.generate_expression(&f.value)?;
19949        if let Some(null_handling) = f.null_handling {
19950            self.write_space();
19951            match null_handling {
19952                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19953                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19954            }
19955        }
19956        self.write(")");
19957        if let Some(ref filter) = f.filter {
19958            self.write_space();
19959            self.write_keyword("FILTER");
19960            self.write("(");
19961            self.write_keyword("WHERE");
19962            self.write_space();
19963            self.generate_expression(filter)?;
19964            self.write(")");
19965        }
19966        Ok(())
19967    }
19968
19969    // Type casting/conversion generators
19970
19971    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19972        use crate::dialects::DialectType;
19973
19974        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19975        if self.config.dialect == Some(DialectType::Redshift) {
19976            self.write_keyword("CAST");
19977            self.write("(");
19978            self.generate_expression(&f.this)?;
19979            self.write_space();
19980            self.write_keyword("AS");
19981            self.write_space();
19982            self.generate_data_type(&f.to)?;
19983            self.write(")");
19984            return Ok(());
19985        }
19986
19987        self.write_keyword("CONVERT");
19988        self.write("(");
19989        self.generate_data_type(&f.to)?;
19990        self.write(", ");
19991        self.generate_expression(&f.this)?;
19992        if let Some(ref style) = f.style {
19993            self.write(", ");
19994            self.generate_expression(style)?;
19995        }
19996        self.write(")");
19997        Ok(())
19998    }
19999
20000    // Additional expression generators
20001
20002    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
20003        if f.colon {
20004            // DuckDB syntax: LAMBDA x : expr
20005            self.write_keyword("LAMBDA");
20006            self.write_space();
20007            for (i, param) in f.parameters.iter().enumerate() {
20008                if i > 0 {
20009                    self.write(", ");
20010                }
20011                self.generate_identifier(param)?;
20012            }
20013            self.write(" : ");
20014        } else {
20015            // Standard syntax: x -> expr or (x, y) -> expr
20016            if f.parameters.len() == 1 {
20017                self.generate_identifier(&f.parameters[0])?;
20018            } else {
20019                self.write("(");
20020                for (i, param) in f.parameters.iter().enumerate() {
20021                    if i > 0 {
20022                        self.write(", ");
20023                    }
20024                    self.generate_identifier(param)?;
20025                }
20026                self.write(")");
20027            }
20028            self.write(" -> ");
20029        }
20030        self.generate_expression(&f.body)
20031    }
20032
20033    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
20034        self.generate_identifier(&f.name)?;
20035        match f.separator {
20036            NamedArgSeparator::DArrow => self.write(" => "),
20037            NamedArgSeparator::ColonEq => self.write(" := "),
20038            NamedArgSeparator::Eq => self.write(" = "),
20039        }
20040        self.generate_expression(&f.value)
20041    }
20042
20043    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
20044        self.write_keyword(&f.prefix);
20045        self.write(" ");
20046        self.generate_expression(&f.this)
20047    }
20048
20049    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
20050        match f.style {
20051            ParameterStyle::Question => self.write("?"),
20052            ParameterStyle::Dollar => {
20053                self.write("$");
20054                if let Some(idx) = f.index {
20055                    self.write(&idx.to_string());
20056                } else if let Some(ref name) = f.name {
20057                    // Session variable like $x or $query_id
20058                    self.write(name);
20059                }
20060            }
20061            ParameterStyle::DollarBrace => {
20062                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20063                self.write("${");
20064                if let Some(ref name) = f.name {
20065                    self.write(name);
20066                }
20067                if let Some(ref expr) = f.expression {
20068                    self.write(":");
20069                    self.write(expr);
20070                }
20071                self.write("}");
20072            }
20073            ParameterStyle::Colon => {
20074                self.write(":");
20075                if let Some(idx) = f.index {
20076                    self.write(&idx.to_string());
20077                } else if let Some(ref name) = f.name {
20078                    self.write(name);
20079                }
20080            }
20081            ParameterStyle::At => {
20082                self.write("@");
20083                if let Some(ref name) = f.name {
20084                    if f.string_quoted {
20085                        self.write("'");
20086                        self.write(name);
20087                        self.write("'");
20088                    } else if f.quoted {
20089                        self.write("\"");
20090                        self.write(name);
20091                        self.write("\"");
20092                    } else {
20093                        self.write(name);
20094                    }
20095                }
20096            }
20097            ParameterStyle::DoubleAt => {
20098                self.write("@@");
20099                if let Some(ref name) = f.name {
20100                    self.write(name);
20101                }
20102            }
20103            ParameterStyle::DoubleDollar => {
20104                self.write("$$");
20105                if let Some(ref name) = f.name {
20106                    self.write(name);
20107                }
20108            }
20109            ParameterStyle::Percent => {
20110                if let Some(ref name) = f.name {
20111                    // %(name)s format
20112                    self.write("%(");
20113                    self.write(name);
20114                    self.write(")s");
20115                } else {
20116                    // %s format
20117                    self.write("%s");
20118                }
20119            }
20120            ParameterStyle::Brace => {
20121                // Spark/Databricks widget template variable: {name}
20122                // ClickHouse query parameter may include kind: {name: Type}
20123                self.write("{");
20124                if let Some(ref name) = f.name {
20125                    self.write(name);
20126                }
20127                if let Some(ref expr) = f.expression {
20128                    self.write(": ");
20129                    self.write(expr);
20130                }
20131                self.write("}");
20132            }
20133        }
20134        Ok(())
20135    }
20136
20137    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20138        self.write("?");
20139        if let Some(idx) = f.index {
20140            self.write(&idx.to_string());
20141        }
20142        Ok(())
20143    }
20144
20145    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20146        if f.is_block {
20147            self.write("/*");
20148            self.write(&f.text);
20149            self.write("*/");
20150        } else {
20151            self.write("--");
20152            self.write(&f.text);
20153        }
20154        Ok(())
20155    }
20156
20157    // Additional predicate generators
20158
20159    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20160        self.generate_expression(&f.this)?;
20161        if f.not {
20162            self.write_space();
20163            self.write_keyword("NOT");
20164        }
20165        self.write_space();
20166        self.write_keyword("SIMILAR TO");
20167        self.write_space();
20168        self.generate_expression(&f.pattern)?;
20169        if let Some(ref escape) = f.escape {
20170            self.write_space();
20171            self.write_keyword("ESCAPE");
20172            self.write_space();
20173            self.generate_expression(escape)?;
20174        }
20175        Ok(())
20176    }
20177
20178    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20179        self.generate_expression(&f.this)?;
20180        self.write_space();
20181        // Output comparison operator if present
20182        if let Some(op) = &f.op {
20183            match op {
20184                QuantifiedOp::Eq => self.write("="),
20185                QuantifiedOp::Neq => self.write("<>"),
20186                QuantifiedOp::Lt => self.write("<"),
20187                QuantifiedOp::Lte => self.write("<="),
20188                QuantifiedOp::Gt => self.write(">"),
20189                QuantifiedOp::Gte => self.write(">="),
20190            }
20191            self.write_space();
20192        }
20193        self.write_keyword(name);
20194
20195        // If the child is a Subquery, it provides its own parens — output with space
20196        if matches!(&f.subquery, Expression::Subquery(_)) {
20197            self.write_space();
20198            self.generate_expression(&f.subquery)?;
20199        } else {
20200            self.write("(");
20201
20202            let is_statement = matches!(
20203                &f.subquery,
20204                Expression::Select(_)
20205                    | Expression::Union(_)
20206                    | Expression::Intersect(_)
20207                    | Expression::Except(_)
20208            );
20209
20210            if self.config.pretty && is_statement {
20211                self.write_newline();
20212                self.indent_level += 1;
20213                self.write_indent();
20214            }
20215            self.generate_expression(&f.subquery)?;
20216            if self.config.pretty && is_statement {
20217                self.write_newline();
20218                self.indent_level -= 1;
20219                self.write_indent();
20220            }
20221            self.write(")");
20222        }
20223        Ok(())
20224    }
20225
20226    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20227        // Check if this is a simple binary form (this OVERLAPS expression)
20228        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20229            self.generate_expression(this)?;
20230            self.write_space();
20231            self.write_keyword("OVERLAPS");
20232            self.write_space();
20233            self.generate_expression(expr)?;
20234        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20235            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20236        {
20237            // Full ANSI form: (a, b) OVERLAPS (c, d)
20238            self.write("(");
20239            self.generate_expression(ls)?;
20240            self.write(", ");
20241            self.generate_expression(le)?;
20242            self.write(")");
20243            self.write_space();
20244            self.write_keyword("OVERLAPS");
20245            self.write_space();
20246            self.write("(");
20247            self.generate_expression(rs)?;
20248            self.write(", ");
20249            self.generate_expression(re)?;
20250            self.write(")");
20251        }
20252        Ok(())
20253    }
20254
20255    // Type conversion generators
20256
20257    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20258        use crate::dialects::DialectType;
20259
20260        // SingleStore uses !:> syntax for try cast
20261        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20262            self.generate_expression(&cast.this)?;
20263            self.write(" !:> ");
20264            self.generate_data_type(&cast.to)?;
20265            return Ok(());
20266        }
20267
20268        // Teradata uses TRYCAST (no underscore)
20269        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20270            self.write_keyword("TRYCAST");
20271            self.write("(");
20272            self.generate_expression(&cast.this)?;
20273            self.write_space();
20274            self.write_keyword("AS");
20275            self.write_space();
20276            self.generate_data_type(&cast.to)?;
20277            self.write(")");
20278            return Ok(());
20279        }
20280
20281        // Dialects without TRY_CAST: generate as regular CAST
20282        let keyword = if matches!(
20283            self.config.dialect,
20284            Some(DialectType::Hive)
20285                | Some(DialectType::MySQL)
20286                | Some(DialectType::SQLite)
20287                | Some(DialectType::Oracle)
20288                | Some(DialectType::ClickHouse)
20289                | Some(DialectType::Redshift)
20290                | Some(DialectType::PostgreSQL)
20291                | Some(DialectType::StarRocks)
20292                | Some(DialectType::Doris)
20293        ) {
20294            "CAST"
20295        } else {
20296            "TRY_CAST"
20297        };
20298
20299        self.write_keyword(keyword);
20300        self.write("(");
20301        self.generate_expression(&cast.this)?;
20302        self.write_space();
20303        self.write_keyword("AS");
20304        self.write_space();
20305        self.generate_data_type(&cast.to)?;
20306
20307        // Output FORMAT clause if present
20308        if let Some(format) = &cast.format {
20309            self.write_space();
20310            self.write_keyword("FORMAT");
20311            self.write_space();
20312            self.generate_expression(format)?;
20313        }
20314
20315        self.write(")");
20316        Ok(())
20317    }
20318
20319    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20320        self.write_keyword("SAFE_CAST");
20321        self.write("(");
20322        self.generate_expression(&cast.this)?;
20323        self.write_space();
20324        self.write_keyword("AS");
20325        self.write_space();
20326        self.generate_data_type(&cast.to)?;
20327
20328        // Output FORMAT clause if present
20329        if let Some(format) = &cast.format {
20330            self.write_space();
20331            self.write_keyword("FORMAT");
20332            self.write_space();
20333            self.generate_expression(format)?;
20334        }
20335
20336        self.write(")");
20337        Ok(())
20338    }
20339
20340    // Array/struct/map access generators
20341
20342    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20343        self.generate_expression(&s.this)?;
20344        self.write("[");
20345        self.generate_expression(&s.index)?;
20346        self.write("]");
20347        Ok(())
20348    }
20349
20350    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20351        self.generate_expression(&d.this)?;
20352        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20353        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20354        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20355            && matches!(
20356                &d.this,
20357                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20358            );
20359        if use_colon {
20360            self.write(":");
20361        } else {
20362            self.write(".");
20363        }
20364        self.generate_identifier(&d.field)
20365    }
20366
20367    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20368        self.generate_expression(&m.this)?;
20369        self.write(".");
20370        // Method names after a dot should not be quoted based on reserved keywords
20371        // Only quote if explicitly marked as quoted in the AST
20372        if m.method.quoted {
20373            let q = self.config.identifier_quote;
20374            self.write(&format!("{}{}{}", q, m.method.name, q));
20375        } else {
20376            self.write(&m.method.name);
20377        }
20378        self.write("(");
20379        for (i, arg) in m.args.iter().enumerate() {
20380            if i > 0 {
20381                self.write(", ");
20382            }
20383            self.generate_expression(arg)?;
20384        }
20385        self.write(")");
20386        Ok(())
20387    }
20388
20389    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20390        // Check if we need to wrap the inner expression in parentheses
20391        // JSON arrow expressions have lower precedence than array subscript
20392        let needs_parens = matches!(
20393            &s.this,
20394            Expression::JsonExtract(f) if f.arrow_syntax
20395        ) || matches!(
20396            &s.this,
20397            Expression::JsonExtractScalar(f) if f.arrow_syntax
20398        );
20399
20400        if needs_parens {
20401            self.write("(");
20402        }
20403        self.generate_expression(&s.this)?;
20404        if needs_parens {
20405            self.write(")");
20406        }
20407        self.write("[");
20408        if let Some(start) = &s.start {
20409            self.generate_expression(start)?;
20410        }
20411        self.write(":");
20412        if let Some(end) = &s.end {
20413            self.generate_expression(end)?;
20414        }
20415        self.write("]");
20416        Ok(())
20417    }
20418
20419    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20420        // Generate left expression, but skip trailing comments if they're already in left_comments
20421        // to avoid duplication (comments are captured as both expr.trailing_comments
20422        // and BinaryOp.left_comments during parsing)
20423        match &op.left {
20424            Expression::Column(col) => {
20425                // Generate column with trailing comments but skip them if they're
20426                // already captured in BinaryOp.left_comments to avoid duplication
20427                if let Some(table) = &col.table {
20428                    self.generate_identifier(table)?;
20429                    self.write(".");
20430                }
20431                self.generate_identifier(&col.name)?;
20432                // Oracle-style join marker (+)
20433                if col.join_mark && self.config.supports_column_join_marks {
20434                    self.write(" (+)");
20435                }
20436                // Output column trailing comments if they're not already in left_comments
20437                if op.left_comments.is_empty() {
20438                    for comment in &col.trailing_comments {
20439                        self.write_space();
20440                        self.write_formatted_comment(comment);
20441                    }
20442                }
20443            }
20444            Expression::Add(inner_op)
20445            | Expression::Sub(inner_op)
20446            | Expression::Mul(inner_op)
20447            | Expression::Div(inner_op)
20448            | Expression::Concat(inner_op) => {
20449                // Generate binary op without its trailing comments
20450                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20451                    Expression::Add(_) => "+",
20452                    Expression::Sub(_) => "-",
20453                    Expression::Mul(_) => "*",
20454                    Expression::Div(_) => "/",
20455                    Expression::Concat(_) => "||",
20456                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20457                })?;
20458            }
20459            _ => {
20460                self.generate_expression(&op.left)?;
20461            }
20462        }
20463        // Output comments after left operand
20464        for comment in &op.left_comments {
20465            self.write_space();
20466            self.write_formatted_comment(comment);
20467        }
20468        if self.config.pretty
20469            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20470            && (operator == "AND" || operator == "OR")
20471        {
20472            self.write_newline();
20473            self.write_indent();
20474            self.write_keyword(operator);
20475        } else {
20476            self.write_space();
20477            if operator.chars().all(|c| c.is_alphabetic()) {
20478                self.write_keyword(operator);
20479            } else {
20480                self.write(operator);
20481            }
20482        }
20483        // Output comments after operator (before right operand)
20484        for comment in &op.operator_comments {
20485            self.write_space();
20486            self.write_formatted_comment(comment);
20487        }
20488        self.write_space();
20489        self.generate_expression(&op.right)?;
20490        // Output trailing comments after right operand
20491        for comment in &op.trailing_comments {
20492            self.write_space();
20493            self.write_formatted_comment(comment);
20494        }
20495        Ok(())
20496    }
20497
20498    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
20499        let keyword = connector.keyword();
20500        let Some(terms) = self.flatten_connector_terms(op, connector) else {
20501            return self.generate_binary_op(op, keyword);
20502        };
20503
20504        self.generate_expression(terms[0])?;
20505        for term in terms.iter().skip(1) {
20506            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20507                self.write_newline();
20508                self.write_indent();
20509                self.write_keyword(keyword);
20510            } else {
20511                self.write_space();
20512                self.write_keyword(keyword);
20513            }
20514            self.write_space();
20515            self.generate_expression(term)?;
20516        }
20517
20518        Ok(())
20519    }
20520
20521    fn flatten_connector_terms<'a>(
20522        &self,
20523        root: &'a BinaryOp,
20524        connector: ConnectorOperator,
20525    ) -> Option<Vec<&'a Expression>> {
20526        if !root.left_comments.is_empty()
20527            || !root.operator_comments.is_empty()
20528            || !root.trailing_comments.is_empty()
20529        {
20530            return None;
20531        }
20532
20533        let mut terms = Vec::new();
20534        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
20535
20536        while let Some(expr) = stack.pop() {
20537            match (connector, expr) {
20538                (ConnectorOperator::And, Expression::And(inner))
20539                    if inner.left_comments.is_empty()
20540                        && inner.operator_comments.is_empty()
20541                        && inner.trailing_comments.is_empty() =>
20542                {
20543                    stack.push(&inner.right);
20544                    stack.push(&inner.left);
20545                }
20546                (ConnectorOperator::Or, Expression::Or(inner))
20547                    if inner.left_comments.is_empty()
20548                        && inner.operator_comments.is_empty()
20549                        && inner.trailing_comments.is_empty() =>
20550                {
20551                    stack.push(&inner.right);
20552                    stack.push(&inner.left);
20553                }
20554                _ => terms.push(expr),
20555            }
20556        }
20557
20558        if terms.len() > 1 {
20559            Some(terms)
20560        } else {
20561            None
20562        }
20563    }
20564
20565    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20566    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20567        self.generate_expression(&op.left)?;
20568        self.write_space();
20569        // Drill backtick-quotes ILIKE
20570        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20571            self.write("`ILIKE`");
20572        } else {
20573            self.write_keyword(operator);
20574        }
20575        if let Some(quantifier) = &op.quantifier {
20576            self.write_space();
20577            self.write_keyword(quantifier);
20578        }
20579        self.write_space();
20580        self.generate_expression(&op.right)?;
20581        if let Some(escape) = &op.escape {
20582            self.write_space();
20583            self.write_keyword("ESCAPE");
20584            self.write_space();
20585            self.generate_expression(escape)?;
20586        }
20587        Ok(())
20588    }
20589
20590    /// Generate null-safe equality
20591    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20592    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20593        use crate::dialects::DialectType;
20594        self.generate_expression(&op.left)?;
20595        self.write_space();
20596        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20597            self.write("<=>");
20598        } else {
20599            self.write_keyword("IS NOT DISTINCT FROM");
20600        }
20601        self.write_space();
20602        self.generate_expression(&op.right)?;
20603        Ok(())
20604    }
20605
20606    /// Generate IS DISTINCT FROM (null-safe inequality)
20607    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20608        self.generate_expression(&op.left)?;
20609        self.write_space();
20610        self.write_keyword("IS DISTINCT FROM");
20611        self.write_space();
20612        self.generate_expression(&op.right)?;
20613        Ok(())
20614    }
20615
20616    /// Generate binary op without trailing comments (used when nested inside another binary op)
20617    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20618        // Generate left expression, but skip trailing comments
20619        match &op.left {
20620            Expression::Column(col) => {
20621                if let Some(table) = &col.table {
20622                    self.generate_identifier(table)?;
20623                    self.write(".");
20624                }
20625                self.generate_identifier(&col.name)?;
20626                // Oracle-style join marker (+)
20627                if col.join_mark && self.config.supports_column_join_marks {
20628                    self.write(" (+)");
20629                }
20630            }
20631            Expression::Add(inner_op)
20632            | Expression::Sub(inner_op)
20633            | Expression::Mul(inner_op)
20634            | Expression::Div(inner_op)
20635            | Expression::Concat(inner_op) => {
20636                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20637                    Expression::Add(_) => "+",
20638                    Expression::Sub(_) => "-",
20639                    Expression::Mul(_) => "*",
20640                    Expression::Div(_) => "/",
20641                    Expression::Concat(_) => "||",
20642                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20643                })?;
20644            }
20645            _ => {
20646                self.generate_expression(&op.left)?;
20647            }
20648        }
20649        // Output left_comments
20650        for comment in &op.left_comments {
20651            self.write_space();
20652            self.write_formatted_comment(comment);
20653        }
20654        self.write_space();
20655        if operator.chars().all(|c| c.is_alphabetic()) {
20656            self.write_keyword(operator);
20657        } else {
20658            self.write(operator);
20659        }
20660        // Output operator_comments
20661        for comment in &op.operator_comments {
20662            self.write_space();
20663            self.write_formatted_comment(comment);
20664        }
20665        self.write_space();
20666        // Generate right expression, but skip trailing comments if it's a Column
20667        // (the parent's left_comments will output them)
20668        match &op.right {
20669            Expression::Column(col) => {
20670                if let Some(table) = &col.table {
20671                    self.generate_identifier(table)?;
20672                    self.write(".");
20673                }
20674                self.generate_identifier(&col.name)?;
20675                // Oracle-style join marker (+)
20676                if col.join_mark && self.config.supports_column_join_marks {
20677                    self.write(" (+)");
20678                }
20679            }
20680            _ => {
20681                self.generate_expression(&op.right)?;
20682            }
20683        }
20684        // Skip trailing_comments - parent will handle them via its left_comments
20685        Ok(())
20686    }
20687
20688    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20689        if operator.chars().all(|c| c.is_alphabetic()) {
20690            self.write_keyword(operator);
20691            self.write_space();
20692        } else {
20693            self.write(operator);
20694            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20695            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20696                self.write_space();
20697            }
20698        }
20699        self.generate_expression(&op.this)
20700    }
20701
20702    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20703        // Generic mode supports two styles for negated IN:
20704        // - Prefix: NOT a IN (...)
20705        // - Infix:  a NOT IN (...)
20706        let is_generic =
20707            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20708        let use_prefix_not =
20709            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20710        if use_prefix_not {
20711            self.write_keyword("NOT");
20712            self.write_space();
20713        }
20714        self.generate_expression(&in_expr.this)?;
20715        if in_expr.global {
20716            self.write_space();
20717            self.write_keyword("GLOBAL");
20718        }
20719        if in_expr.not && !use_prefix_not {
20720            self.write_space();
20721            self.write_keyword("NOT");
20722        }
20723        self.write_space();
20724        self.write_keyword("IN");
20725
20726        // BigQuery: IN UNNEST(expr)
20727        if let Some(unnest_expr) = &in_expr.unnest {
20728            self.write_space();
20729            self.write_keyword("UNNEST");
20730            self.write("(");
20731            self.generate_expression(unnest_expr)?;
20732            self.write(")");
20733            return Ok(());
20734        }
20735
20736        if let Some(query) = &in_expr.query {
20737            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20738            // vs a subquery (col IN (SELECT ...))
20739            let is_bare = in_expr.expressions.is_empty()
20740                && !matches!(
20741                    query,
20742                    Expression::Select(_)
20743                        | Expression::Union(_)
20744                        | Expression::Intersect(_)
20745                        | Expression::Except(_)
20746                        | Expression::Subquery(_)
20747                );
20748            if is_bare {
20749                // Bare identifier: no parentheses
20750                self.write_space();
20751                self.generate_expression(query)?;
20752            } else {
20753                // Subquery: with parentheses
20754                self.write(" (");
20755                let is_statement = matches!(
20756                    query,
20757                    Expression::Select(_)
20758                        | Expression::Union(_)
20759                        | Expression::Intersect(_)
20760                        | Expression::Except(_)
20761                        | Expression::Subquery(_)
20762                );
20763                if self.config.pretty && is_statement {
20764                    self.write_newline();
20765                    self.indent_level += 1;
20766                    self.write_indent();
20767                }
20768                self.generate_expression(query)?;
20769                if self.config.pretty && is_statement {
20770                    self.write_newline();
20771                    self.indent_level -= 1;
20772                    self.write_indent();
20773                }
20774                self.write(")");
20775            }
20776        } else {
20777            // DuckDB: IN without parentheses for single expression that is NOT a literal
20778            // (array/list membership like 'red' IN tbl.flags)
20779            // ClickHouse: IN without parentheses for single non-array expressions
20780            let is_duckdb = matches!(
20781                self.config.dialect,
20782                Some(crate::dialects::DialectType::DuckDB)
20783            );
20784            let is_clickhouse = matches!(
20785                self.config.dialect,
20786                Some(crate::dialects::DialectType::ClickHouse)
20787            );
20788            let single_expr = in_expr.expressions.len() == 1;
20789            if is_clickhouse && single_expr {
20790                if let Expression::Array(arr) = &in_expr.expressions[0] {
20791                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20792                    self.write(" (");
20793                    for (i, expr) in arr.expressions.iter().enumerate() {
20794                        if i > 0 {
20795                            self.write(", ");
20796                        }
20797                        self.generate_expression(expr)?;
20798                    }
20799                    self.write(")");
20800                } else {
20801                    self.write_space();
20802                    self.generate_expression(&in_expr.expressions[0])?;
20803                }
20804            } else {
20805                let is_bare_ref = single_expr
20806                    && matches!(
20807                        &in_expr.expressions[0],
20808                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20809                    );
20810                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20811                    // Bare field reference (no parens in source): IN identifier
20812                    // Also DuckDB: IN without parentheses for array/list membership
20813                    self.write_space();
20814                    self.generate_expression(&in_expr.expressions[0])?;
20815                } else {
20816                    // Standard IN (list)
20817                    self.write(" (");
20818                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20819                        if i > 0 {
20820                            self.write(", ");
20821                        }
20822                        self.generate_expression(expr)?;
20823                    }
20824                    self.write(")");
20825                }
20826            }
20827        }
20828
20829        Ok(())
20830    }
20831
20832    fn generate_between(&mut self, between: &Between) -> Result<()> {
20833        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20834        let use_prefix_not = between.not
20835            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20836        if use_prefix_not {
20837            self.write_keyword("NOT");
20838            self.write_space();
20839        }
20840        self.generate_expression(&between.this)?;
20841        if between.not && !use_prefix_not {
20842            self.write_space();
20843            self.write_keyword("NOT");
20844        }
20845        self.write_space();
20846        self.write_keyword("BETWEEN");
20847        // Emit SYMMETRIC/ASYMMETRIC if present
20848        if let Some(sym) = between.symmetric {
20849            if sym {
20850                self.write(" SYMMETRIC");
20851            } else {
20852                self.write(" ASYMMETRIC");
20853            }
20854        }
20855        self.write_space();
20856        self.generate_expression(&between.low)?;
20857        self.write_space();
20858        self.write_keyword("AND");
20859        self.write_space();
20860        self.generate_expression(&between.high)
20861    }
20862
20863    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20864        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20865        let use_prefix_not = is_null.not
20866            && (self.config.dialect.is_none()
20867                || self.config.dialect == Some(DialectType::Generic)
20868                || is_null.postfix_form);
20869        if use_prefix_not {
20870            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20871            self.write_keyword("NOT");
20872            self.write_space();
20873            self.generate_expression(&is_null.this)?;
20874            self.write_space();
20875            self.write_keyword("IS");
20876            self.write_space();
20877            self.write_keyword("NULL");
20878        } else {
20879            self.generate_expression(&is_null.this)?;
20880            self.write_space();
20881            self.write_keyword("IS");
20882            if is_null.not {
20883                self.write_space();
20884                self.write_keyword("NOT");
20885            }
20886            self.write_space();
20887            self.write_keyword("NULL");
20888        }
20889        Ok(())
20890    }
20891
20892    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20893        self.generate_expression(&is_true.this)?;
20894        self.write_space();
20895        self.write_keyword("IS");
20896        if is_true.not {
20897            self.write_space();
20898            self.write_keyword("NOT");
20899        }
20900        self.write_space();
20901        self.write_keyword("TRUE");
20902        Ok(())
20903    }
20904
20905    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20906        self.generate_expression(&is_false.this)?;
20907        self.write_space();
20908        self.write_keyword("IS");
20909        if is_false.not {
20910            self.write_space();
20911            self.write_keyword("NOT");
20912        }
20913        self.write_space();
20914        self.write_keyword("FALSE");
20915        Ok(())
20916    }
20917
20918    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20919        self.generate_expression(&is_json.this)?;
20920        self.write_space();
20921        self.write_keyword("IS");
20922        if is_json.negated {
20923            self.write_space();
20924            self.write_keyword("NOT");
20925        }
20926        self.write_space();
20927        self.write_keyword("JSON");
20928
20929        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20930        if let Some(ref json_type) = is_json.json_type {
20931            self.write_space();
20932            self.write_keyword(json_type);
20933        }
20934
20935        // Output key uniqueness constraint if specified
20936        match &is_json.unique_keys {
20937            Some(JsonUniqueKeys::With) => {
20938                self.write_space();
20939                self.write_keyword("WITH UNIQUE KEYS");
20940            }
20941            Some(JsonUniqueKeys::Without) => {
20942                self.write_space();
20943                self.write_keyword("WITHOUT UNIQUE KEYS");
20944            }
20945            Some(JsonUniqueKeys::Shorthand) => {
20946                self.write_space();
20947                self.write_keyword("UNIQUE KEYS");
20948            }
20949            None => {}
20950        }
20951
20952        Ok(())
20953    }
20954
20955    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20956        self.generate_expression(&is_expr.left)?;
20957        self.write_space();
20958        self.write_keyword("IS");
20959        self.write_space();
20960        self.generate_expression(&is_expr.right)
20961    }
20962
20963    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20964        if exists.not {
20965            self.write_keyword("NOT");
20966            self.write_space();
20967        }
20968        self.write_keyword("EXISTS");
20969        self.write("(");
20970        let is_statement = matches!(
20971            &exists.this,
20972            Expression::Select(_)
20973                | Expression::Union(_)
20974                | Expression::Intersect(_)
20975                | Expression::Except(_)
20976        );
20977        if self.config.pretty && is_statement {
20978            self.write_newline();
20979            self.indent_level += 1;
20980            self.write_indent();
20981            self.generate_expression(&exists.this)?;
20982            self.write_newline();
20983            self.indent_level -= 1;
20984            self.write_indent();
20985            self.write(")");
20986        } else {
20987            self.generate_expression(&exists.this)?;
20988            self.write(")");
20989        }
20990        Ok(())
20991    }
20992
20993    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20994        self.generate_expression(&op.left)?;
20995        self.write_space();
20996        self.write_keyword("MEMBER OF");
20997        self.write("(");
20998        self.generate_expression(&op.right)?;
20999        self.write(")");
21000        Ok(())
21001    }
21002
21003    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
21004        if subquery.lateral {
21005            self.write_keyword("LATERAL");
21006            self.write_space();
21007        }
21008
21009        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
21010        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
21011        // to carry the LIMIT modifier without adding more parens
21012        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
21013            matches!(
21014                &p.this,
21015                Expression::Select(_)
21016                    | Expression::Union(_)
21017                    | Expression::Intersect(_)
21018                    | Expression::Except(_)
21019                    | Expression::Subquery(_)
21020            )
21021        } else {
21022            false
21023        };
21024
21025        // Check if inner expression is a statement for pretty formatting
21026        let is_statement = matches!(
21027            &subquery.this,
21028            Expression::Select(_)
21029                | Expression::Union(_)
21030                | Expression::Intersect(_)
21031                | Expression::Except(_)
21032                | Expression::Merge(_)
21033        );
21034
21035        if !skip_outer_parens {
21036            self.write("(");
21037            if self.config.pretty && is_statement {
21038                self.write_newline();
21039                self.indent_level += 1;
21040                self.write_indent();
21041            }
21042        }
21043        self.generate_expression(&subquery.this)?;
21044
21045        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
21046        if subquery.modifiers_inside {
21047            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
21048            if let Some(order_by) = &subquery.order_by {
21049                self.write_space();
21050                self.write_keyword("ORDER BY");
21051                self.write_space();
21052                for (i, ord) in order_by.expressions.iter().enumerate() {
21053                    if i > 0 {
21054                        self.write(", ");
21055                    }
21056                    self.generate_ordered(ord)?;
21057                }
21058            }
21059
21060            if let Some(limit) = &subquery.limit {
21061                self.write_space();
21062                self.write_keyword("LIMIT");
21063                self.write_space();
21064                self.generate_expression(&limit.this)?;
21065                if limit.percent {
21066                    self.write_space();
21067                    self.write_keyword("PERCENT");
21068                }
21069            }
21070
21071            if let Some(offset) = &subquery.offset {
21072                self.write_space();
21073                self.write_keyword("OFFSET");
21074                self.write_space();
21075                self.generate_expression(&offset.this)?;
21076            }
21077        }
21078
21079        if !skip_outer_parens {
21080            if self.config.pretty && is_statement {
21081                self.write_newline();
21082                self.indent_level -= 1;
21083                self.write_indent();
21084            }
21085            self.write(")");
21086        }
21087
21088        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21089        if !subquery.modifiers_inside {
21090            if let Some(order_by) = &subquery.order_by {
21091                self.write_space();
21092                self.write_keyword("ORDER BY");
21093                self.write_space();
21094                for (i, ord) in order_by.expressions.iter().enumerate() {
21095                    if i > 0 {
21096                        self.write(", ");
21097                    }
21098                    self.generate_ordered(ord)?;
21099                }
21100            }
21101
21102            if let Some(limit) = &subquery.limit {
21103                self.write_space();
21104                self.write_keyword("LIMIT");
21105                self.write_space();
21106                self.generate_expression(&limit.this)?;
21107                if limit.percent {
21108                    self.write_space();
21109                    self.write_keyword("PERCENT");
21110                }
21111            }
21112
21113            if let Some(offset) = &subquery.offset {
21114                self.write_space();
21115                self.write_keyword("OFFSET");
21116                self.write_space();
21117                self.generate_expression(&offset.this)?;
21118            }
21119
21120            // Generate DISTRIBUTE BY (Hive/Spark)
21121            if let Some(distribute_by) = &subquery.distribute_by {
21122                self.write_space();
21123                self.write_keyword("DISTRIBUTE BY");
21124                self.write_space();
21125                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21126                    if i > 0 {
21127                        self.write(", ");
21128                    }
21129                    self.generate_expression(expr)?;
21130                }
21131            }
21132
21133            // Generate SORT BY (Hive/Spark)
21134            if let Some(sort_by) = &subquery.sort_by {
21135                self.write_space();
21136                self.write_keyword("SORT BY");
21137                self.write_space();
21138                for (i, ord) in sort_by.expressions.iter().enumerate() {
21139                    if i > 0 {
21140                        self.write(", ");
21141                    }
21142                    self.generate_ordered(ord)?;
21143                }
21144            }
21145
21146            // Generate CLUSTER BY (Hive/Spark)
21147            if let Some(cluster_by) = &subquery.cluster_by {
21148                self.write_space();
21149                self.write_keyword("CLUSTER BY");
21150                self.write_space();
21151                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21152                    if i > 0 {
21153                        self.write(", ");
21154                    }
21155                    self.generate_ordered(ord)?;
21156                }
21157            }
21158        }
21159
21160        if let Some(alias) = &subquery.alias {
21161            self.write_space();
21162            // Oracle doesn't use AS for subquery aliases
21163            let skip_as = matches!(
21164                self.config.dialect,
21165                Some(crate::dialects::DialectType::Oracle)
21166            );
21167            if !skip_as {
21168                self.write_keyword("AS");
21169                self.write_space();
21170            }
21171            self.generate_identifier(alias)?;
21172            if !subquery.column_aliases.is_empty() {
21173                self.write("(");
21174                for (i, col) in subquery.column_aliases.iter().enumerate() {
21175                    if i > 0 {
21176                        self.write(", ");
21177                    }
21178                    self.generate_identifier(col)?;
21179                }
21180                self.write(")");
21181            }
21182        }
21183        // Output trailing comments
21184        for comment in &subquery.trailing_comments {
21185            self.write(" ");
21186            self.write_formatted_comment(comment);
21187        }
21188        Ok(())
21189    }
21190
21191    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21192        // Generate WITH clause if present
21193        if let Some(ref with) = pivot.with {
21194            self.generate_with(with)?;
21195            self.write_space();
21196        }
21197
21198        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21199
21200        // Check for Redshift UNPIVOT in FROM clause:
21201        // UNPIVOT expr [AS val AT attr]
21202        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21203        let is_redshift_unpivot = pivot.unpivot
21204            && pivot.expressions.is_empty()
21205            && pivot.fields.is_empty()
21206            && pivot.using.is_empty()
21207            && pivot.into.is_none()
21208            && !matches!(&pivot.this, Expression::Null(_));
21209
21210        if is_redshift_unpivot {
21211            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21212            self.write_keyword("UNPIVOT");
21213            self.write_space();
21214            self.generate_expression(&pivot.this)?;
21215            // Alias - for Redshift it can be "val AT attr" format
21216            if let Some(alias) = &pivot.alias {
21217                self.write_space();
21218                self.write_keyword("AS");
21219                self.write_space();
21220                // The alias might contain " AT " for the attr part
21221                self.write(&alias.name);
21222            }
21223            return Ok(());
21224        }
21225
21226        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21227        let is_simplified = !pivot.using.is_empty()
21228            || pivot.into.is_some()
21229            || (pivot.fields.is_empty()
21230                && !pivot.expressions.is_empty()
21231                && !matches!(&pivot.this, Expression::Null(_)));
21232
21233        if is_simplified {
21234            // DuckDB simplified syntax:
21235            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21236            //   UNPIVOT table ON cols INTO NAME col VALUE col
21237            self.write_keyword(direction);
21238            self.write_space();
21239            self.generate_expression(&pivot.this)?;
21240
21241            if !pivot.expressions.is_empty() {
21242                self.write_space();
21243                self.write_keyword("ON");
21244                self.write_space();
21245                for (i, expr) in pivot.expressions.iter().enumerate() {
21246                    if i > 0 {
21247                        self.write(", ");
21248                    }
21249                    self.generate_expression(expr)?;
21250                }
21251            }
21252
21253            // INTO (for UNPIVOT)
21254            if let Some(into) = &pivot.into {
21255                self.write_space();
21256                self.write_keyword("INTO");
21257                self.write_space();
21258                self.generate_expression(into)?;
21259            }
21260
21261            // USING (for PIVOT)
21262            if !pivot.using.is_empty() {
21263                self.write_space();
21264                self.write_keyword("USING");
21265                self.write_space();
21266                for (i, expr) in pivot.using.iter().enumerate() {
21267                    if i > 0 {
21268                        self.write(", ");
21269                    }
21270                    self.generate_expression(expr)?;
21271                }
21272            }
21273
21274            // GROUP BY
21275            if let Some(group) = &pivot.group {
21276                self.write_space();
21277                self.generate_expression(group)?;
21278            }
21279        } else {
21280            // Standard syntax:
21281            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21282            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21283            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21284            if !matches!(&pivot.this, Expression::Null(_)) {
21285                self.generate_expression(&pivot.this)?;
21286                self.write_space();
21287            }
21288            self.write_keyword(direction);
21289            self.write("(");
21290
21291            // Aggregation expressions
21292            for (i, expr) in pivot.expressions.iter().enumerate() {
21293                if i > 0 {
21294                    self.write(", ");
21295                }
21296                self.generate_expression(expr)?;
21297            }
21298
21299            // FOR...IN fields
21300            if !pivot.fields.is_empty() {
21301                if !pivot.expressions.is_empty() {
21302                    self.write_space();
21303                }
21304                self.write_keyword("FOR");
21305                self.write_space();
21306                for (i, field) in pivot.fields.iter().enumerate() {
21307                    if i > 0 {
21308                        self.write_space();
21309                    }
21310                    // field is an In expression: column IN (values)
21311                    self.generate_expression(field)?;
21312                }
21313            }
21314
21315            // DEFAULT ON NULL
21316            if let Some(default_val) = &pivot.default_on_null {
21317                self.write_space();
21318                self.write_keyword("DEFAULT ON NULL");
21319                self.write(" (");
21320                self.generate_expression(default_val)?;
21321                self.write(")");
21322            }
21323
21324            // GROUP BY inside PIVOT parens
21325            if let Some(group) = &pivot.group {
21326                self.write_space();
21327                self.generate_expression(group)?;
21328            }
21329
21330            self.write(")");
21331        }
21332
21333        // Alias
21334        if let Some(alias) = &pivot.alias {
21335            self.write_space();
21336            self.write_keyword("AS");
21337            self.write_space();
21338            self.generate_identifier(alias)?;
21339        }
21340
21341        Ok(())
21342    }
21343
21344    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21345        self.generate_expression(&unpivot.this)?;
21346        self.write_space();
21347        self.write_keyword("UNPIVOT");
21348        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21349        if let Some(include) = unpivot.include_nulls {
21350            self.write_space();
21351            if include {
21352                self.write_keyword("INCLUDE NULLS");
21353            } else {
21354                self.write_keyword("EXCLUDE NULLS");
21355            }
21356            self.write_space();
21357        }
21358        self.write("(");
21359        if unpivot.value_column_parenthesized {
21360            self.write("(");
21361        }
21362        self.generate_identifier(&unpivot.value_column)?;
21363        // Output additional value columns if present
21364        for extra_col in &unpivot.extra_value_columns {
21365            self.write(", ");
21366            self.generate_identifier(extra_col)?;
21367        }
21368        if unpivot.value_column_parenthesized {
21369            self.write(")");
21370        }
21371        self.write_space();
21372        self.write_keyword("FOR");
21373        self.write_space();
21374        self.generate_identifier(&unpivot.name_column)?;
21375        self.write_space();
21376        self.write_keyword("IN");
21377        self.write(" (");
21378        for (i, col) in unpivot.columns.iter().enumerate() {
21379            if i > 0 {
21380                self.write(", ");
21381            }
21382            self.generate_expression(col)?;
21383        }
21384        self.write("))");
21385        if let Some(alias) = &unpivot.alias {
21386            self.write_space();
21387            self.write_keyword("AS");
21388            self.write_space();
21389            self.generate_identifier(alias)?;
21390        }
21391        Ok(())
21392    }
21393
21394    fn generate_values(&mut self, values: &Values) -> Result<()> {
21395        self.write_keyword("VALUES");
21396        for (i, row) in values.expressions.iter().enumerate() {
21397            if i > 0 {
21398                self.write(",");
21399            }
21400            self.write(" (");
21401            for (j, expr) in row.expressions.iter().enumerate() {
21402                if j > 0 {
21403                    self.write(", ");
21404                }
21405                self.generate_expression(expr)?;
21406            }
21407            self.write(")");
21408        }
21409        if let Some(alias) = &values.alias {
21410            self.write_space();
21411            self.write_keyword("AS");
21412            self.write_space();
21413            self.generate_identifier(alias)?;
21414            if !values.column_aliases.is_empty() {
21415                self.write("(");
21416                for (i, col) in values.column_aliases.iter().enumerate() {
21417                    if i > 0 {
21418                        self.write(", ");
21419                    }
21420                    self.generate_identifier(col)?;
21421                }
21422                self.write(")");
21423            }
21424        }
21425        Ok(())
21426    }
21427
21428    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21429        // Apply struct name inheritance for target dialects that need it
21430        let needs_inheritance = matches!(
21431            self.config.dialect,
21432            Some(DialectType::DuckDB)
21433                | Some(DialectType::Spark)
21434                | Some(DialectType::Databricks)
21435                | Some(DialectType::Hive)
21436                | Some(DialectType::Snowflake)
21437                | Some(DialectType::Presto)
21438                | Some(DialectType::Trino)
21439        );
21440        let propagated: Vec<Expression>;
21441        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21442            propagated = Self::inherit_struct_field_names(&arr.expressions);
21443            &propagated
21444        } else {
21445            &arr.expressions
21446        };
21447
21448        // Generic mode: ARRAY(1, 2, 3) with parentheses
21449        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21450        let use_parens =
21451            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21452        if !self.config.array_bracket_only {
21453            self.write_keyword("ARRAY");
21454        }
21455        if use_parens {
21456            self.write("(");
21457        } else {
21458            self.write("[");
21459        }
21460        for (i, expr) in expressions.iter().enumerate() {
21461            if i > 0 {
21462                self.write(", ");
21463            }
21464            self.generate_expression(expr)?;
21465        }
21466        if use_parens {
21467            self.write(")");
21468        } else {
21469            self.write("]");
21470        }
21471        Ok(())
21472    }
21473
21474    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21475        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21476        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21477        if tuple.expressions.len() == 2 {
21478            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21479                // First element is the function/expression, second is the TableAlias
21480                self.generate_expression(&tuple.expressions[0])?;
21481                self.write_space();
21482                self.write_keyword("AS");
21483                self.write_space();
21484                self.generate_expression(&tuple.expressions[1])?;
21485                return Ok(());
21486            }
21487        }
21488
21489        // In pretty mode, format long tuples with each element on a new line
21490        // Only expand if total width exceeds threshold
21491        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21492            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21493            for expr in &tuple.expressions {
21494                expr_strings.push(self.generate_to_string(expr)?);
21495            }
21496            self.too_wide(&expr_strings)
21497        } else {
21498            false
21499        };
21500
21501        if expand_tuple {
21502            self.write("(");
21503            self.write_newline();
21504            self.indent_level += 1;
21505            for (i, expr) in tuple.expressions.iter().enumerate() {
21506                if i > 0 {
21507                    self.write(",");
21508                    self.write_newline();
21509                }
21510                self.write_indent();
21511                self.generate_expression(expr)?;
21512            }
21513            self.indent_level -= 1;
21514            self.write_newline();
21515            self.write_indent();
21516            self.write(")");
21517        } else {
21518            self.write("(");
21519            for (i, expr) in tuple.expressions.iter().enumerate() {
21520                if i > 0 {
21521                    self.write(", ");
21522                }
21523                self.generate_expression(expr)?;
21524            }
21525            self.write(")");
21526        }
21527        Ok(())
21528    }
21529
21530    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21531        self.generate_expression(&pipe.this)?;
21532        self.write(" |> ");
21533        self.generate_expression(&pipe.expression)?;
21534        Ok(())
21535    }
21536
21537    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21538        self.generate_expression(&ordered.this)?;
21539        if ordered.desc {
21540            self.write_space();
21541            self.write_keyword("DESC");
21542        } else if ordered.explicit_asc {
21543            self.write_space();
21544            self.write_keyword("ASC");
21545        }
21546        if let Some(nulls_first) = ordered.nulls_first {
21547            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21548            // for the dialect. Different dialects have different NULL ordering defaults:
21549            //
21550            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21551            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21552            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21553            //
21554            // nulls_are_small (Spark, Hive, BigQuery, most others):
21555            //   - ASC: NULLS FIRST is default
21556            //   - DESC: NULLS LAST is default
21557            //
21558            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21559            //   - NULLS LAST is always the default regardless of sort direction
21560            let is_asc = !ordered.desc;
21561            let is_nulls_are_large = matches!(
21562                self.config.dialect,
21563                Some(DialectType::Oracle)
21564                    | Some(DialectType::PostgreSQL)
21565                    | Some(DialectType::Redshift)
21566                    | Some(DialectType::Snowflake)
21567            );
21568            let is_nulls_are_last = matches!(
21569                self.config.dialect,
21570                Some(DialectType::Dremio)
21571                    | Some(DialectType::DuckDB)
21572                    | Some(DialectType::Presto)
21573                    | Some(DialectType::Trino)
21574                    | Some(DialectType::Athena)
21575                    | Some(DialectType::ClickHouse)
21576                    | Some(DialectType::Drill)
21577                    | Some(DialectType::Exasol)
21578            );
21579
21580            // Check if the NULLS ordering matches the default for this dialect
21581            let is_default_nulls = if is_nulls_are_large {
21582                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21583                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21584            } else if is_nulls_are_last {
21585                // For nulls_are_last: NULLS LAST is always default
21586                !nulls_first
21587            } else {
21588                false
21589            };
21590
21591            if !is_default_nulls {
21592                self.write_space();
21593                self.write_keyword("NULLS");
21594                self.write_space();
21595                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21596            }
21597        }
21598        // WITH FILL clause (ClickHouse)
21599        if let Some(ref with_fill) = ordered.with_fill {
21600            self.write_space();
21601            self.generate_with_fill(with_fill)?;
21602        }
21603        Ok(())
21604    }
21605
21606    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21607    fn write_clickhouse_type(&mut self, type_str: &str) {
21608        if self.clickhouse_nullable_depth < 0 {
21609            // Map key context: don't wrap in Nullable
21610            self.write(type_str);
21611        } else {
21612            self.write(&format!("Nullable({})", type_str));
21613        }
21614    }
21615
21616    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21617        use crate::dialects::DialectType;
21618
21619        match dt {
21620            DataType::Boolean => {
21621                // Dialect-specific boolean type mappings
21622                match self.config.dialect {
21623                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21624                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21625                    Some(DialectType::Oracle) => {
21626                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21627                        self.write_keyword("NUMBER(1)")
21628                    }
21629                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21630                    _ => self.write_keyword("BOOLEAN"),
21631                }
21632            }
21633            DataType::TinyInt { length } => {
21634                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21635                // Dremio maps TINYINT to INT
21636                // ClickHouse maps TINYINT to Int8
21637                match self.config.dialect {
21638                    Some(DialectType::PostgreSQL)
21639                    | Some(DialectType::Redshift)
21640                    | Some(DialectType::Oracle)
21641                    | Some(DialectType::Exasol) => {
21642                        self.write_keyword("SMALLINT");
21643                    }
21644                    Some(DialectType::Teradata) => {
21645                        // Teradata uses BYTEINT for smallest integer
21646                        self.write_keyword("BYTEINT");
21647                    }
21648                    Some(DialectType::Dremio) => {
21649                        // Dremio maps TINYINT to INT
21650                        self.write_keyword("INT");
21651                    }
21652                    Some(DialectType::ClickHouse) => {
21653                        self.write_clickhouse_type("Int8");
21654                    }
21655                    _ => {
21656                        self.write_keyword("TINYINT");
21657                    }
21658                }
21659                if let Some(n) = length {
21660                    if !matches!(
21661                        self.config.dialect,
21662                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21663                    ) {
21664                        self.write(&format!("({})", n));
21665                    }
21666                }
21667            }
21668            DataType::SmallInt { length } => {
21669                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21670                match self.config.dialect {
21671                    Some(DialectType::Dremio) => {
21672                        self.write_keyword("INT");
21673                    }
21674                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21675                        self.write_keyword("INTEGER");
21676                    }
21677                    Some(DialectType::BigQuery) => {
21678                        self.write_keyword("INT64");
21679                    }
21680                    Some(DialectType::ClickHouse) => {
21681                        self.write_clickhouse_type("Int16");
21682                    }
21683                    _ => {
21684                        self.write_keyword("SMALLINT");
21685                        if let Some(n) = length {
21686                            self.write(&format!("({})", n));
21687                        }
21688                    }
21689                }
21690            }
21691            DataType::Int {
21692                length,
21693                integer_spelling,
21694            } => {
21695                // BigQuery uses INT64 for INT
21696                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21697                    self.write_keyword("INT64");
21698                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21699                    self.write_clickhouse_type("Int32");
21700                } else {
21701                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21702                    let use_integer = match self.config.dialect {
21703                        Some(DialectType::TSQL)
21704                        | Some(DialectType::Fabric)
21705                        | Some(DialectType::Presto)
21706                        | Some(DialectType::Trino)
21707                        | Some(DialectType::SQLite)
21708                        | Some(DialectType::Redshift) => true,
21709                        // Databricks preserves the original spelling
21710                        Some(DialectType::Databricks) => *integer_spelling,
21711                        _ => false,
21712                    };
21713                    if use_integer {
21714                        self.write_keyword("INTEGER");
21715                    } else {
21716                        self.write_keyword("INT");
21717                    }
21718                    if let Some(n) = length {
21719                        self.write(&format!("({})", n));
21720                    }
21721                }
21722            }
21723            DataType::BigInt { length } => {
21724                // Dialect-specific bigint type mappings
21725                match self.config.dialect {
21726                    Some(DialectType::Oracle) => {
21727                        // Oracle doesn't have BIGINT, uses INT
21728                        self.write_keyword("INT");
21729                    }
21730                    Some(DialectType::ClickHouse) => {
21731                        self.write_clickhouse_type("Int64");
21732                    }
21733                    _ => {
21734                        self.write_keyword("BIGINT");
21735                        if let Some(n) = length {
21736                            self.write(&format!("({})", n));
21737                        }
21738                    }
21739                }
21740            }
21741            DataType::Float {
21742                precision,
21743                scale,
21744                real_spelling,
21745            } => {
21746                // Dialect-specific float type mappings
21747                // If real_spelling is true, preserve REAL; otherwise use dialect default
21748                // Spark/Hive don't support REAL, always use FLOAT
21749                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21750                    self.write_clickhouse_type("Float32");
21751                } else if *real_spelling
21752                    && !matches!(
21753                        self.config.dialect,
21754                        Some(DialectType::Spark)
21755                            | Some(DialectType::Databricks)
21756                            | Some(DialectType::Hive)
21757                            | Some(DialectType::Snowflake)
21758                            | Some(DialectType::MySQL)
21759                            | Some(DialectType::BigQuery)
21760                    )
21761                {
21762                    self.write_keyword("REAL")
21763                } else {
21764                    match self.config.dialect {
21765                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21766                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21767                        _ => self.write_keyword("FLOAT"),
21768                    }
21769                }
21770                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21771                // Spark/Hive don't support FLOAT(precision)
21772                if !matches!(
21773                    self.config.dialect,
21774                    Some(DialectType::Spark)
21775                        | Some(DialectType::Databricks)
21776                        | Some(DialectType::Hive)
21777                        | Some(DialectType::Presto)
21778                        | Some(DialectType::Trino)
21779                ) {
21780                    if let Some(p) = precision {
21781                        self.write(&format!("({}", p));
21782                        if let Some(s) = scale {
21783                            self.write(&format!(", {})", s));
21784                        } else {
21785                            self.write(")");
21786                        }
21787                    }
21788                }
21789            }
21790            DataType::Double { precision, scale } => {
21791                // Dialect-specific double type mappings
21792                match self.config.dialect {
21793                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21794                        self.write_keyword("FLOAT")
21795                    } // SQL Server/Fabric FLOAT is double
21796                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21797                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21798                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21799                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21800                    Some(DialectType::PostgreSQL)
21801                    | Some(DialectType::Redshift)
21802                    | Some(DialectType::Teradata)
21803                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21804                    _ => self.write_keyword("DOUBLE"),
21805                }
21806                // MySQL supports DOUBLE(precision, scale)
21807                if let Some(p) = precision {
21808                    self.write(&format!("({}", p));
21809                    if let Some(s) = scale {
21810                        self.write(&format!(", {})", s));
21811                    } else {
21812                        self.write(")");
21813                    }
21814                }
21815            }
21816            DataType::Decimal { precision, scale } => {
21817                // Dialect-specific decimal type mappings
21818                match self.config.dialect {
21819                    Some(DialectType::ClickHouse) => {
21820                        self.write("Decimal");
21821                        if let Some(p) = precision {
21822                            self.write(&format!("({}", p));
21823                            if let Some(s) = scale {
21824                                self.write(&format!(", {}", s));
21825                            }
21826                            self.write(")");
21827                        }
21828                    }
21829                    Some(DialectType::Oracle) => {
21830                        // Oracle uses NUMBER instead of DECIMAL
21831                        self.write_keyword("NUMBER");
21832                        if let Some(p) = precision {
21833                            self.write(&format!("({}", p));
21834                            if let Some(s) = scale {
21835                                self.write(&format!(", {}", s));
21836                            }
21837                            self.write(")");
21838                        }
21839                    }
21840                    Some(DialectType::BigQuery) => {
21841                        // BigQuery uses NUMERIC instead of DECIMAL
21842                        self.write_keyword("NUMERIC");
21843                        if let Some(p) = precision {
21844                            self.write(&format!("({}", p));
21845                            if let Some(s) = scale {
21846                                self.write(&format!(", {}", s));
21847                            }
21848                            self.write(")");
21849                        }
21850                    }
21851                    _ => {
21852                        self.write_keyword("DECIMAL");
21853                        if let Some(p) = precision {
21854                            self.write(&format!("({}", p));
21855                            if let Some(s) = scale {
21856                                self.write(&format!(", {}", s));
21857                            }
21858                            self.write(")");
21859                        }
21860                    }
21861                }
21862            }
21863            DataType::Char { length } => {
21864                // Dialect-specific char type mappings
21865                match self.config.dialect {
21866                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21867                        // DuckDB/SQLite maps CHAR to TEXT
21868                        self.write_keyword("TEXT");
21869                    }
21870                    Some(DialectType::Hive)
21871                    | Some(DialectType::Spark)
21872                    | Some(DialectType::Databricks) => {
21873                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21874                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21875                        if length.is_some()
21876                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21877                        {
21878                            self.write_keyword("CHAR");
21879                            if let Some(n) = length {
21880                                self.write(&format!("({})", n));
21881                            }
21882                        } else {
21883                            self.write_keyword("STRING");
21884                        }
21885                    }
21886                    Some(DialectType::Dremio) => {
21887                        // Dremio maps CHAR to VARCHAR
21888                        self.write_keyword("VARCHAR");
21889                        if let Some(n) = length {
21890                            self.write(&format!("({})", n));
21891                        }
21892                    }
21893                    _ => {
21894                        self.write_keyword("CHAR");
21895                        if let Some(n) = length {
21896                            self.write(&format!("({})", n));
21897                        }
21898                    }
21899                }
21900            }
21901            DataType::VarChar {
21902                length,
21903                parenthesized_length,
21904            } => {
21905                // Dialect-specific varchar type mappings
21906                match self.config.dialect {
21907                    Some(DialectType::Oracle) => {
21908                        self.write_keyword("VARCHAR2");
21909                        if let Some(n) = length {
21910                            self.write(&format!("({})", n));
21911                        }
21912                    }
21913                    Some(DialectType::DuckDB) => {
21914                        // DuckDB maps VARCHAR to TEXT, preserving length
21915                        self.write_keyword("TEXT");
21916                        if let Some(n) = length {
21917                            self.write(&format!("({})", n));
21918                        }
21919                    }
21920                    Some(DialectType::SQLite) => {
21921                        // SQLite maps VARCHAR to TEXT, preserving length
21922                        self.write_keyword("TEXT");
21923                        if let Some(n) = length {
21924                            self.write(&format!("({})", n));
21925                        }
21926                    }
21927                    Some(DialectType::MySQL) if length.is_none() => {
21928                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21929                        self.write_keyword("TEXT");
21930                    }
21931                    Some(DialectType::Hive)
21932                    | Some(DialectType::Spark)
21933                    | Some(DialectType::Databricks)
21934                        if length.is_none() =>
21935                    {
21936                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21937                        self.write_keyword("STRING");
21938                    }
21939                    _ => {
21940                        self.write_keyword("VARCHAR");
21941                        if let Some(n) = length {
21942                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21943                            if *parenthesized_length {
21944                                self.write(&format!("(({}))", n));
21945                            } else {
21946                                self.write(&format!("({})", n));
21947                            }
21948                        }
21949                    }
21950                }
21951            }
21952            DataType::Text => {
21953                // Dialect-specific text type mappings
21954                match self.config.dialect {
21955                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21956                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21957                        self.write_keyword("VARCHAR(MAX)")
21958                    }
21959                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21960                    Some(DialectType::Snowflake)
21961                    | Some(DialectType::Dremio)
21962                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21963                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21964                    Some(DialectType::Presto)
21965                    | Some(DialectType::Trino)
21966                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21967                    Some(DialectType::Spark)
21968                    | Some(DialectType::Databricks)
21969                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21970                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21971                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21972                        self.write_keyword("STRING")
21973                    }
21974                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21975                    _ => self.write_keyword("TEXT"),
21976                }
21977            }
21978            DataType::TextWithLength { length } => {
21979                // TEXT(n) - dialect-specific type with length
21980                match self.config.dialect {
21981                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21982                    Some(DialectType::Hive)
21983                    | Some(DialectType::Spark)
21984                    | Some(DialectType::Databricks) => {
21985                        self.write(&format!("VARCHAR({})", length));
21986                    }
21987                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21988                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21989                    Some(DialectType::Snowflake)
21990                    | Some(DialectType::Presto)
21991                    | Some(DialectType::Trino)
21992                    | Some(DialectType::Athena)
21993                    | Some(DialectType::Drill)
21994                    | Some(DialectType::Dremio) => {
21995                        self.write(&format!("VARCHAR({})", length));
21996                    }
21997                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21998                        self.write(&format!("VARCHAR({})", length))
21999                    }
22000                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22001                        self.write(&format!("STRING({})", length))
22002                    }
22003                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22004                    _ => self.write(&format!("TEXT({})", length)),
22005                }
22006            }
22007            DataType::String { length } => {
22008                // STRING type with optional length (BigQuery STRING(n))
22009                match self.config.dialect {
22010                    Some(DialectType::ClickHouse) => {
22011                        // ClickHouse uses String with specific casing
22012                        self.write("String");
22013                        if let Some(n) = length {
22014                            self.write(&format!("({})", n));
22015                        }
22016                    }
22017                    Some(DialectType::BigQuery)
22018                    | Some(DialectType::Hive)
22019                    | Some(DialectType::Spark)
22020                    | Some(DialectType::Databricks)
22021                    | Some(DialectType::StarRocks)
22022                    | Some(DialectType::Doris) => {
22023                        self.write_keyword("STRING");
22024                        if let Some(n) = length {
22025                            self.write(&format!("({})", n));
22026                        }
22027                    }
22028                    Some(DialectType::PostgreSQL) => {
22029                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
22030                        if let Some(n) = length {
22031                            self.write_keyword("VARCHAR");
22032                            self.write(&format!("({})", n));
22033                        } else {
22034                            self.write_keyword("TEXT");
22035                        }
22036                    }
22037                    Some(DialectType::Redshift) => {
22038                        // Redshift: STRING -> VARCHAR(MAX)
22039                        if let Some(n) = length {
22040                            self.write_keyword("VARCHAR");
22041                            self.write(&format!("({})", n));
22042                        } else {
22043                            self.write_keyword("VARCHAR(MAX)");
22044                        }
22045                    }
22046                    Some(DialectType::MySQL) => {
22047                        // MySQL doesn't have STRING - use VARCHAR or TEXT
22048                        if let Some(n) = length {
22049                            self.write_keyword("VARCHAR");
22050                            self.write(&format!("({})", n));
22051                        } else {
22052                            self.write_keyword("TEXT");
22053                        }
22054                    }
22055                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22056                        // TSQL: STRING -> VARCHAR(MAX)
22057                        if let Some(n) = length {
22058                            self.write_keyword("VARCHAR");
22059                            self.write(&format!("({})", n));
22060                        } else {
22061                            self.write_keyword("VARCHAR(MAX)");
22062                        }
22063                    }
22064                    Some(DialectType::Oracle) => {
22065                        // Oracle: STRING -> CLOB
22066                        self.write_keyword("CLOB");
22067                    }
22068                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22069                        // DuckDB/Materialize uses TEXT for string types
22070                        self.write_keyword("TEXT");
22071                        if let Some(n) = length {
22072                            self.write(&format!("({})", n));
22073                        }
22074                    }
22075                    Some(DialectType::Presto)
22076                    | Some(DialectType::Trino)
22077                    | Some(DialectType::Drill)
22078                    | Some(DialectType::Dremio) => {
22079                        // Presto/Trino/Drill use VARCHAR for string types
22080                        self.write_keyword("VARCHAR");
22081                        if let Some(n) = length {
22082                            self.write(&format!("({})", n));
22083                        }
22084                    }
22085                    Some(DialectType::Snowflake) => {
22086                        // Snowflake: STRING stays as STRING (identity/DDL)
22087                        // CAST context STRING -> VARCHAR is handled in generate_cast
22088                        self.write_keyword("STRING");
22089                        if let Some(n) = length {
22090                            self.write(&format!("({})", n));
22091                        }
22092                    }
22093                    _ => {
22094                        // Default: output STRING with optional length
22095                        self.write_keyword("STRING");
22096                        if let Some(n) = length {
22097                            self.write(&format!("({})", n));
22098                        }
22099                    }
22100                }
22101            }
22102            DataType::Binary { length } => {
22103                // Dialect-specific binary type mappings
22104                match self.config.dialect {
22105                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22106                        self.write_keyword("BYTEA");
22107                        if let Some(n) = length {
22108                            self.write(&format!("({})", n));
22109                        }
22110                    }
22111                    Some(DialectType::Redshift) => {
22112                        self.write_keyword("VARBYTE");
22113                        if let Some(n) = length {
22114                            self.write(&format!("({})", n));
22115                        }
22116                    }
22117                    Some(DialectType::DuckDB)
22118                    | Some(DialectType::SQLite)
22119                    | Some(DialectType::Oracle) => {
22120                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22121                        self.write_keyword("BLOB");
22122                        if let Some(n) = length {
22123                            self.write(&format!("({})", n));
22124                        }
22125                    }
22126                    Some(DialectType::Presto)
22127                    | Some(DialectType::Trino)
22128                    | Some(DialectType::Athena)
22129                    | Some(DialectType::Drill)
22130                    | Some(DialectType::Dremio) => {
22131                        // These dialects map BINARY to VARBINARY
22132                        self.write_keyword("VARBINARY");
22133                        if let Some(n) = length {
22134                            self.write(&format!("({})", n));
22135                        }
22136                    }
22137                    Some(DialectType::ClickHouse) => {
22138                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22139                        if self.clickhouse_nullable_depth < 0 {
22140                            self.write("BINARY");
22141                        } else {
22142                            self.write("Nullable(BINARY");
22143                        }
22144                        if let Some(n) = length {
22145                            self.write(&format!("({})", n));
22146                        }
22147                        if self.clickhouse_nullable_depth >= 0 {
22148                            self.write(")");
22149                        }
22150                    }
22151                    _ => {
22152                        self.write_keyword("BINARY");
22153                        if let Some(n) = length {
22154                            self.write(&format!("({})", n));
22155                        }
22156                    }
22157                }
22158            }
22159            DataType::VarBinary { length } => {
22160                // Dialect-specific varbinary type mappings
22161                match self.config.dialect {
22162                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22163                        self.write_keyword("BYTEA");
22164                        if let Some(n) = length {
22165                            self.write(&format!("({})", n));
22166                        }
22167                    }
22168                    Some(DialectType::Redshift) => {
22169                        self.write_keyword("VARBYTE");
22170                        if let Some(n) = length {
22171                            self.write(&format!("({})", n));
22172                        }
22173                    }
22174                    Some(DialectType::DuckDB)
22175                    | Some(DialectType::SQLite)
22176                    | Some(DialectType::Oracle) => {
22177                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22178                        self.write_keyword("BLOB");
22179                        if let Some(n) = length {
22180                            self.write(&format!("({})", n));
22181                        }
22182                    }
22183                    Some(DialectType::Exasol) => {
22184                        // Exasol maps VARBINARY to VARCHAR
22185                        self.write_keyword("VARCHAR");
22186                    }
22187                    Some(DialectType::Spark)
22188                    | Some(DialectType::Hive)
22189                    | Some(DialectType::Databricks) => {
22190                        // Spark/Hive use BINARY instead of VARBINARY
22191                        self.write_keyword("BINARY");
22192                        if let Some(n) = length {
22193                            self.write(&format!("({})", n));
22194                        }
22195                    }
22196                    Some(DialectType::ClickHouse) => {
22197                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22198                        self.write_clickhouse_type("String");
22199                    }
22200                    _ => {
22201                        self.write_keyword("VARBINARY");
22202                        if let Some(n) = length {
22203                            self.write(&format!("({})", n));
22204                        }
22205                    }
22206                }
22207            }
22208            DataType::Blob => {
22209                // Dialect-specific blob type mappings
22210                match self.config.dialect {
22211                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22212                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22213                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22214                        self.write_keyword("VARBINARY")
22215                    }
22216                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22217                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22218                    Some(DialectType::Presto)
22219                    | Some(DialectType::Trino)
22220                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22221                    Some(DialectType::DuckDB) => {
22222                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22223                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22224                        self.write_keyword("VARBINARY");
22225                    }
22226                    Some(DialectType::Spark)
22227                    | Some(DialectType::Databricks)
22228                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22229                    Some(DialectType::ClickHouse) => {
22230                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22231                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22232                        // This matches Python sqlglot behavior.
22233                        self.write("Nullable(String)");
22234                    }
22235                    _ => self.write_keyword("BLOB"),
22236                }
22237            }
22238            DataType::Bit { length } => {
22239                // Dialect-specific bit type mappings
22240                match self.config.dialect {
22241                    Some(DialectType::Dremio)
22242                    | Some(DialectType::Spark)
22243                    | Some(DialectType::Databricks)
22244                    | Some(DialectType::Hive)
22245                    | Some(DialectType::Snowflake)
22246                    | Some(DialectType::BigQuery)
22247                    | Some(DialectType::Presto)
22248                    | Some(DialectType::Trino)
22249                    | Some(DialectType::ClickHouse)
22250                    | Some(DialectType::Redshift) => {
22251                        // These dialects don't support BIT type, use BOOLEAN
22252                        self.write_keyword("BOOLEAN");
22253                    }
22254                    _ => {
22255                        self.write_keyword("BIT");
22256                        if let Some(n) = length {
22257                            self.write(&format!("({})", n));
22258                        }
22259                    }
22260                }
22261            }
22262            DataType::VarBit { length } => {
22263                self.write_keyword("VARBIT");
22264                if let Some(n) = length {
22265                    self.write(&format!("({})", n));
22266                }
22267            }
22268            DataType::Date => self.write_keyword("DATE"),
22269            DataType::Time {
22270                precision,
22271                timezone,
22272            } => {
22273                if *timezone {
22274                    // Dialect-specific TIME WITH TIME ZONE output
22275                    match self.config.dialect {
22276                        Some(DialectType::DuckDB) => {
22277                            // DuckDB: TIMETZ (drops precision)
22278                            self.write_keyword("TIMETZ");
22279                        }
22280                        Some(DialectType::PostgreSQL) => {
22281                            // PostgreSQL: TIMETZ or TIMETZ(p)
22282                            self.write_keyword("TIMETZ");
22283                            if let Some(p) = precision {
22284                                self.write(&format!("({})", p));
22285                            }
22286                        }
22287                        _ => {
22288                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22289                            self.write_keyword("TIME");
22290                            if let Some(p) = precision {
22291                                self.write(&format!("({})", p));
22292                            }
22293                            self.write_keyword(" WITH TIME ZONE");
22294                        }
22295                    }
22296                } else {
22297                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22298                    if matches!(
22299                        self.config.dialect,
22300                        Some(DialectType::Spark)
22301                            | Some(DialectType::Databricks)
22302                            | Some(DialectType::Hive)
22303                    ) {
22304                        self.write_keyword("TIMESTAMP");
22305                    } else {
22306                        self.write_keyword("TIME");
22307                        if let Some(p) = precision {
22308                            self.write(&format!("({})", p));
22309                        }
22310                    }
22311                }
22312            }
22313            DataType::Timestamp {
22314                precision,
22315                timezone,
22316            } => {
22317                // Dialect-specific timestamp type mappings
22318                match self.config.dialect {
22319                    Some(DialectType::ClickHouse) => {
22320                        self.write("DateTime");
22321                        if let Some(p) = precision {
22322                            self.write(&format!("({})", p));
22323                        }
22324                    }
22325                    Some(DialectType::TSQL) => {
22326                        if *timezone {
22327                            self.write_keyword("DATETIMEOFFSET");
22328                        } else {
22329                            self.write_keyword("DATETIME2");
22330                        }
22331                        if let Some(p) = precision {
22332                            self.write(&format!("({})", p));
22333                        }
22334                    }
22335                    Some(DialectType::MySQL) => {
22336                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22337                        self.write_keyword("TIMESTAMP");
22338                        if let Some(p) = precision {
22339                            self.write(&format!("({})", p));
22340                        }
22341                    }
22342                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22343                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22344                        self.write_keyword("DATETIME");
22345                        if let Some(p) = precision {
22346                            self.write(&format!("({})", p));
22347                        }
22348                    }
22349                    Some(DialectType::BigQuery) => {
22350                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22351                        if *timezone {
22352                            self.write_keyword("TIMESTAMP");
22353                        } else {
22354                            self.write_keyword("DATETIME");
22355                        }
22356                    }
22357                    Some(DialectType::DuckDB) => {
22358                        // DuckDB: TIMESTAMPTZ shorthand
22359                        if *timezone {
22360                            self.write_keyword("TIMESTAMPTZ");
22361                        } else {
22362                            self.write_keyword("TIMESTAMP");
22363                            if let Some(p) = precision {
22364                                self.write(&format!("({})", p));
22365                            }
22366                        }
22367                    }
22368                    _ => {
22369                        if *timezone && !self.config.tz_to_with_time_zone {
22370                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22371                            self.write_keyword("TIMESTAMPTZ");
22372                            if let Some(p) = precision {
22373                                self.write(&format!("({})", p));
22374                            }
22375                        } else {
22376                            self.write_keyword("TIMESTAMP");
22377                            if let Some(p) = precision {
22378                                self.write(&format!("({})", p));
22379                            }
22380                            if *timezone {
22381                                self.write_space();
22382                                self.write_keyword("WITH TIME ZONE");
22383                            }
22384                        }
22385                    }
22386                }
22387            }
22388            DataType::Interval { unit, to } => {
22389                self.write_keyword("INTERVAL");
22390                if let Some(u) = unit {
22391                    self.write_space();
22392                    self.write_keyword(u);
22393                }
22394                // Handle range intervals like DAY TO HOUR
22395                if let Some(t) = to {
22396                    self.write_space();
22397                    self.write_keyword("TO");
22398                    self.write_space();
22399                    self.write_keyword(t);
22400                }
22401            }
22402            DataType::Json => {
22403                // Dialect-specific JSON type mappings
22404                match self.config.dialect {
22405                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22406                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22407                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22408                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22409                    _ => self.write_keyword("JSON"),
22410                }
22411            }
22412            DataType::JsonB => {
22413                // JSONB is PostgreSQL specific, but Doris also supports it
22414                match self.config.dialect {
22415                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22416                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22417                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22418                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22419                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22420                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22421                }
22422            }
22423            DataType::Uuid => {
22424                // Dialect-specific UUID type mappings
22425                match self.config.dialect {
22426                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22427                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22428                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22429                    Some(DialectType::BigQuery)
22430                    | Some(DialectType::Spark)
22431                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22432                    _ => self.write_keyword("UUID"),
22433                }
22434            }
22435            DataType::Array {
22436                element_type,
22437                dimension,
22438            } => {
22439                // Dialect-specific array syntax
22440                match self.config.dialect {
22441                    Some(DialectType::PostgreSQL)
22442                    | Some(DialectType::Redshift)
22443                    | Some(DialectType::DuckDB) => {
22444                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22445                        self.generate_data_type(element_type)?;
22446                        if let Some(dim) = dimension {
22447                            self.write(&format!("[{}]", dim));
22448                        } else {
22449                            self.write("[]");
22450                        }
22451                    }
22452                    Some(DialectType::BigQuery) => {
22453                        self.write_keyword("ARRAY<");
22454                        self.generate_data_type(element_type)?;
22455                        self.write(">");
22456                    }
22457                    Some(DialectType::Snowflake)
22458                    | Some(DialectType::Presto)
22459                    | Some(DialectType::Trino)
22460                    | Some(DialectType::ClickHouse) => {
22461                        // These dialects use Array(TYPE) parentheses syntax
22462                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22463                            self.write("Array(");
22464                        } else {
22465                            self.write_keyword("ARRAY(");
22466                        }
22467                        self.generate_data_type(element_type)?;
22468                        self.write(")");
22469                    }
22470                    Some(DialectType::TSQL)
22471                    | Some(DialectType::MySQL)
22472                    | Some(DialectType::Oracle) => {
22473                        // These dialects don't have native array types
22474                        // Fall back to JSON or use native workarounds
22475                        match self.config.dialect {
22476                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22477                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22478                            _ => self.write_keyword("JSON"),
22479                        }
22480                    }
22481                    _ => {
22482                        // Default: use angle bracket syntax (ARRAY<T>)
22483                        self.write_keyword("ARRAY<");
22484                        self.generate_data_type(element_type)?;
22485                        self.write(">");
22486                    }
22487                }
22488            }
22489            DataType::List { element_type } => {
22490                // Materialize: element_type LIST (postfix syntax)
22491                self.generate_data_type(element_type)?;
22492                self.write_keyword(" LIST");
22493            }
22494            DataType::Map {
22495                key_type,
22496                value_type,
22497            } => {
22498                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22499                match self.config.dialect {
22500                    Some(DialectType::Materialize) => {
22501                        // Materialize: MAP[key_type => value_type]
22502                        self.write_keyword("MAP[");
22503                        self.generate_data_type(key_type)?;
22504                        self.write(" => ");
22505                        self.generate_data_type(value_type)?;
22506                        self.write("]");
22507                    }
22508                    Some(DialectType::Snowflake)
22509                    | Some(DialectType::RisingWave)
22510                    | Some(DialectType::DuckDB)
22511                    | Some(DialectType::Presto)
22512                    | Some(DialectType::Trino)
22513                    | Some(DialectType::Athena) => {
22514                        self.write_keyword("MAP(");
22515                        self.generate_data_type(key_type)?;
22516                        self.write(", ");
22517                        self.generate_data_type(value_type)?;
22518                        self.write(")");
22519                    }
22520                    Some(DialectType::ClickHouse) => {
22521                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22522                        // Key types must NOT be wrapped in Nullable
22523                        self.write("Map(");
22524                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22525                        self.generate_data_type(key_type)?;
22526                        self.clickhouse_nullable_depth = 0;
22527                        self.write(", ");
22528                        self.generate_data_type(value_type)?;
22529                        self.write(")");
22530                    }
22531                    _ => {
22532                        self.write_keyword("MAP<");
22533                        self.generate_data_type(key_type)?;
22534                        self.write(", ");
22535                        self.generate_data_type(value_type)?;
22536                        self.write(">");
22537                    }
22538                }
22539            }
22540            DataType::Vector {
22541                element_type,
22542                dimension,
22543            } => {
22544                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22545                    // SingleStore format: VECTOR(dimension, type_alias)
22546                    self.write_keyword("VECTOR(");
22547                    if let Some(dim) = dimension {
22548                        self.write(&dim.to_string());
22549                    }
22550                    // Map type back to SingleStore alias
22551                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22552                        DataType::TinyInt { .. } => Some("I8"),
22553                        DataType::SmallInt { .. } => Some("I16"),
22554                        DataType::Int { .. } => Some("I32"),
22555                        DataType::BigInt { .. } => Some("I64"),
22556                        DataType::Float { .. } => Some("F32"),
22557                        DataType::Double { .. } => Some("F64"),
22558                        _ => None,
22559                    });
22560                    if let Some(alias) = type_alias {
22561                        if dimension.is_some() {
22562                            self.write(", ");
22563                        }
22564                        self.write(alias);
22565                    }
22566                    self.write(")");
22567                } else {
22568                    // Snowflake format: VECTOR(type, dimension)
22569                    self.write_keyword("VECTOR(");
22570                    if let Some(ref et) = element_type {
22571                        self.generate_data_type(et)?;
22572                        if dimension.is_some() {
22573                            self.write(", ");
22574                        }
22575                    }
22576                    if let Some(dim) = dimension {
22577                        self.write(&dim.to_string());
22578                    }
22579                    self.write(")");
22580                }
22581            }
22582            DataType::Object { fields, modifier } => {
22583                self.write_keyword("OBJECT(");
22584                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22585                    if i > 0 {
22586                        self.write(", ");
22587                    }
22588                    self.write(name);
22589                    self.write(" ");
22590                    self.generate_data_type(dt)?;
22591                    if *not_null {
22592                        self.write_keyword(" NOT NULL");
22593                    }
22594                }
22595                self.write(")");
22596                if let Some(mod_str) = modifier {
22597                    self.write(" ");
22598                    self.write_keyword(mod_str);
22599                }
22600            }
22601            DataType::Struct { fields, nested } => {
22602                // Dialect-specific struct type mappings
22603                match self.config.dialect {
22604                    Some(DialectType::Snowflake) => {
22605                        // Snowflake maps STRUCT to OBJECT
22606                        self.write_keyword("OBJECT(");
22607                        for (i, field) in fields.iter().enumerate() {
22608                            if i > 0 {
22609                                self.write(", ");
22610                            }
22611                            if !field.name.is_empty() {
22612                                self.write(&field.name);
22613                                self.write(" ");
22614                            }
22615                            self.generate_data_type(&field.data_type)?;
22616                        }
22617                        self.write(")");
22618                    }
22619                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22620                        // Presto/Trino use ROW(name TYPE, ...) syntax
22621                        self.write_keyword("ROW(");
22622                        for (i, field) in fields.iter().enumerate() {
22623                            if i > 0 {
22624                                self.write(", ");
22625                            }
22626                            if !field.name.is_empty() {
22627                                self.write(&field.name);
22628                                self.write(" ");
22629                            }
22630                            self.generate_data_type(&field.data_type)?;
22631                        }
22632                        self.write(")");
22633                    }
22634                    Some(DialectType::DuckDB) => {
22635                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22636                        self.write_keyword("STRUCT(");
22637                        for (i, field) in fields.iter().enumerate() {
22638                            if i > 0 {
22639                                self.write(", ");
22640                            }
22641                            if !field.name.is_empty() {
22642                                self.write(&field.name);
22643                                self.write(" ");
22644                            }
22645                            self.generate_data_type(&field.data_type)?;
22646                        }
22647                        self.write(")");
22648                    }
22649                    Some(DialectType::ClickHouse) => {
22650                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22651                        self.write("Tuple(");
22652                        for (i, field) in fields.iter().enumerate() {
22653                            if i > 0 {
22654                                self.write(", ");
22655                            }
22656                            if !field.name.is_empty() {
22657                                self.write(&field.name);
22658                                self.write(" ");
22659                            }
22660                            self.generate_data_type(&field.data_type)?;
22661                        }
22662                        self.write(")");
22663                    }
22664                    Some(DialectType::SingleStore) => {
22665                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22666                        self.write_keyword("RECORD(");
22667                        for (i, field) in fields.iter().enumerate() {
22668                            if i > 0 {
22669                                self.write(", ");
22670                            }
22671                            if !field.name.is_empty() {
22672                                self.write(&field.name);
22673                                self.write(" ");
22674                            }
22675                            self.generate_data_type(&field.data_type)?;
22676                        }
22677                        self.write(")");
22678                    }
22679                    _ => {
22680                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22681                        let force_angle_brackets = matches!(
22682                            self.config.dialect,
22683                            Some(DialectType::Hive)
22684                                | Some(DialectType::Spark)
22685                                | Some(DialectType::Databricks)
22686                        );
22687                        if *nested && !force_angle_brackets {
22688                            self.write_keyword("STRUCT(");
22689                            for (i, field) in fields.iter().enumerate() {
22690                                if i > 0 {
22691                                    self.write(", ");
22692                                }
22693                                if !field.name.is_empty() {
22694                                    self.write(&field.name);
22695                                    self.write(" ");
22696                                }
22697                                self.generate_data_type(&field.data_type)?;
22698                            }
22699                            self.write(")");
22700                        } else {
22701                            self.write_keyword("STRUCT<");
22702                            for (i, field) in fields.iter().enumerate() {
22703                                if i > 0 {
22704                                    self.write(", ");
22705                                }
22706                                if !field.name.is_empty() {
22707                                    // Named field: name TYPE (with configurable separator for Hive)
22708                                    self.write(&field.name);
22709                                    self.write(self.config.struct_field_sep);
22710                                }
22711                                // For anonymous fields, just output the type
22712                                self.generate_data_type(&field.data_type)?;
22713                                // Spark/Databricks: Output COMMENT clause if present
22714                                if let Some(comment) = &field.comment {
22715                                    self.write(" COMMENT '");
22716                                    self.write(comment);
22717                                    self.write("'");
22718                                }
22719                                // BigQuery: Output OPTIONS clause if present
22720                                if !field.options.is_empty() {
22721                                    self.write(" ");
22722                                    self.generate_options_clause(&field.options)?;
22723                                }
22724                            }
22725                            self.write(">");
22726                        }
22727                    }
22728                }
22729            }
22730            DataType::Enum {
22731                values,
22732                assignments,
22733            } => {
22734                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22735                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22736                if self.config.dialect == Some(DialectType::ClickHouse) {
22737                    self.write("Enum(");
22738                } else {
22739                    self.write_keyword("ENUM(");
22740                }
22741                for (i, val) in values.iter().enumerate() {
22742                    if i > 0 {
22743                        self.write(", ");
22744                    }
22745                    self.write("'");
22746                    self.write(val);
22747                    self.write("'");
22748                    if let Some(Some(assignment)) = assignments.get(i) {
22749                        self.write(" = ");
22750                        self.write(assignment);
22751                    }
22752                }
22753                self.write(")");
22754            }
22755            DataType::Set { values } => {
22756                // MySQL SET type: SET('a', 'b', 'c')
22757                self.write_keyword("SET(");
22758                for (i, val) in values.iter().enumerate() {
22759                    if i > 0 {
22760                        self.write(", ");
22761                    }
22762                    self.write("'");
22763                    self.write(val);
22764                    self.write("'");
22765                }
22766                self.write(")");
22767            }
22768            DataType::Union { fields } => {
22769                // DuckDB UNION type: UNION(num INT, str TEXT)
22770                self.write_keyword("UNION(");
22771                for (i, (name, dt)) in fields.iter().enumerate() {
22772                    if i > 0 {
22773                        self.write(", ");
22774                    }
22775                    if !name.is_empty() {
22776                        self.write(name);
22777                        self.write(" ");
22778                    }
22779                    self.generate_data_type(dt)?;
22780                }
22781                self.write(")");
22782            }
22783            DataType::Nullable { inner } => {
22784                // ClickHouse: Nullable(T), other dialects: just the inner type
22785                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22786                    self.write("Nullable(");
22787                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22788                    let saved_depth = self.clickhouse_nullable_depth;
22789                    self.clickhouse_nullable_depth = -1;
22790                    self.generate_data_type(inner)?;
22791                    self.clickhouse_nullable_depth = saved_depth;
22792                    self.write(")");
22793                } else {
22794                    // Map ClickHouse-specific custom type names to standard types
22795                    match inner.as_ref() {
22796                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22797                            self.generate_data_type(&DataType::Timestamp {
22798                                precision: None,
22799                                timezone: false,
22800                            })?;
22801                        }
22802                        _ => {
22803                            self.generate_data_type(inner)?;
22804                        }
22805                    }
22806                }
22807            }
22808            DataType::Custom { name } => {
22809                // Handle dialect-specific type transformations
22810                let name_upper = name.to_uppercase();
22811                match self.config.dialect {
22812                    Some(DialectType::ClickHouse) => {
22813                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22814                            (name_upper[..idx].to_string(), &name[idx..])
22815                        } else {
22816                            (name_upper.clone(), "")
22817                        };
22818                        let mapped = match base_upper.as_str() {
22819                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22820                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22821                            "DATETIME64" => "DateTime64",
22822                            "DATE32" => "Date32",
22823                            "INT" => "Int32",
22824                            "MEDIUMINT" => "Int32",
22825                            "INT8" => "Int8",
22826                            "INT16" => "Int16",
22827                            "INT32" => "Int32",
22828                            "INT64" => "Int64",
22829                            "INT128" => "Int128",
22830                            "INT256" => "Int256",
22831                            "UINT8" => "UInt8",
22832                            "UINT16" => "UInt16",
22833                            "UINT32" => "UInt32",
22834                            "UINT64" => "UInt64",
22835                            "UINT128" => "UInt128",
22836                            "UINT256" => "UInt256",
22837                            "FLOAT32" => "Float32",
22838                            "FLOAT64" => "Float64",
22839                            "DECIMAL32" => "Decimal32",
22840                            "DECIMAL64" => "Decimal64",
22841                            "DECIMAL128" => "Decimal128",
22842                            "DECIMAL256" => "Decimal256",
22843                            "ENUM" => "Enum",
22844                            "ENUM8" => "Enum8",
22845                            "ENUM16" => "Enum16",
22846                            "FIXEDSTRING" => "FixedString",
22847                            "NESTED" => "Nested",
22848                            "LOWCARDINALITY" => "LowCardinality",
22849                            "NULLABLE" => "Nullable",
22850                            "IPV4" => "IPv4",
22851                            "IPV6" => "IPv6",
22852                            "POINT" => "Point",
22853                            "RING" => "Ring",
22854                            "LINESTRING" => "LineString",
22855                            "MULTILINESTRING" => "MultiLineString",
22856                            "POLYGON" => "Polygon",
22857                            "MULTIPOLYGON" => "MultiPolygon",
22858                            "AGGREGATEFUNCTION" => "AggregateFunction",
22859                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22860                            "DYNAMIC" => "Dynamic",
22861                            _ => "",
22862                        };
22863                        if mapped.is_empty() {
22864                            self.write(name);
22865                        } else {
22866                            self.write(mapped);
22867                            self.write(suffix);
22868                        }
22869                    }
22870                    Some(DialectType::MySQL)
22871                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22872                    {
22873                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22874                        self.write_keyword("TIMESTAMP");
22875                    }
22876                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22877                        self.write_keyword("SQL_VARIANT");
22878                    }
22879                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22880                        self.write_keyword("DECIMAL(38, 5)");
22881                    }
22882                    Some(DialectType::Exasol) => {
22883                        // Exasol type mappings for custom types
22884                        match name_upper.as_str() {
22885                            // Binary types → VARCHAR
22886                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22887                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22888                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22889                            // Integer types
22890                            "MEDIUMINT" => self.write_keyword("INT"),
22891                            // Decimal types → DECIMAL
22892                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22893                                self.write_keyword("DECIMAL")
22894                            }
22895                            // Timestamp types
22896                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22897                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22898                            _ => self.write(name),
22899                        }
22900                    }
22901                    Some(DialectType::Dremio) => {
22902                        // Dremio type mappings for custom types
22903                        match name_upper.as_str() {
22904                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22905                            "ARRAY" => self.write_keyword("LIST"),
22906                            "NCHAR" => self.write_keyword("VARCHAR"),
22907                            _ => self.write(name),
22908                        }
22909                    }
22910                    // Map dialect-specific custom types to standard SQL types for other dialects
22911                    _ => {
22912                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22913                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22914                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22915                        } else {
22916                            (name_upper.clone(), None)
22917                        };
22918
22919                        match base_upper.as_str() {
22920                            "INT64"
22921                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22922                            {
22923                                self.write_keyword("BIGINT");
22924                            }
22925                            "FLOAT64"
22926                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22927                            {
22928                                self.write_keyword("DOUBLE");
22929                            }
22930                            "BOOL"
22931                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22932                            {
22933                                self.write_keyword("BOOLEAN");
22934                            }
22935                            "BYTES"
22936                                if matches!(
22937                                    self.config.dialect,
22938                                    Some(DialectType::Spark)
22939                                        | Some(DialectType::Hive)
22940                                        | Some(DialectType::Databricks)
22941                                ) =>
22942                            {
22943                                self.write_keyword("BINARY");
22944                            }
22945                            "BYTES"
22946                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22947                            {
22948                                self.write_keyword("VARBINARY");
22949                            }
22950                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22951                            "DATETIME2" | "SMALLDATETIME"
22952                                if !matches!(
22953                                    self.config.dialect,
22954                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22955                                ) =>
22956                            {
22957                                // PostgreSQL preserves precision, others don't
22958                                if matches!(
22959                                    self.config.dialect,
22960                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22961                                ) {
22962                                    self.write_keyword("TIMESTAMP");
22963                                    if let Some(args) = _args_str {
22964                                        self.write(args);
22965                                    }
22966                                } else {
22967                                    self.write_keyword("TIMESTAMP");
22968                                }
22969                            }
22970                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22971                            "DATETIMEOFFSET"
22972                                if !matches!(
22973                                    self.config.dialect,
22974                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22975                                ) =>
22976                            {
22977                                if matches!(
22978                                    self.config.dialect,
22979                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22980                                ) {
22981                                    self.write_keyword("TIMESTAMPTZ");
22982                                    if let Some(args) = _args_str {
22983                                        self.write(args);
22984                                    }
22985                                } else {
22986                                    self.write_keyword("TIMESTAMPTZ");
22987                                }
22988                            }
22989                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22990                            "UNIQUEIDENTIFIER"
22991                                if !matches!(
22992                                    self.config.dialect,
22993                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22994                                ) =>
22995                            {
22996                                match self.config.dialect {
22997                                    Some(DialectType::Spark)
22998                                    | Some(DialectType::Databricks)
22999                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23000                                    _ => self.write_keyword("UUID"),
23001                                }
23002                            }
23003                            // TSQL BIT -> BOOLEAN for most dialects
23004                            "BIT"
23005                                if !matches!(
23006                                    self.config.dialect,
23007                                    Some(DialectType::TSQL)
23008                                        | Some(DialectType::Fabric)
23009                                        | Some(DialectType::PostgreSQL)
23010                                        | Some(DialectType::MySQL)
23011                                        | Some(DialectType::DuckDB)
23012                                ) =>
23013                            {
23014                                self.write_keyword("BOOLEAN");
23015                            }
23016                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
23017                            "NVARCHAR"
23018                                if !matches!(
23019                                    self.config.dialect,
23020                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23021                                ) =>
23022                            {
23023                                match self.config.dialect {
23024                                    Some(DialectType::Oracle) => {
23025                                        // Oracle: NVARCHAR -> NVARCHAR2
23026                                        self.write_keyword("NVARCHAR2");
23027                                        if let Some(args) = _args_str {
23028                                            self.write(args);
23029                                        }
23030                                    }
23031                                    Some(DialectType::BigQuery) => {
23032                                        // BigQuery: NVARCHAR -> STRING
23033                                        self.write_keyword("STRING");
23034                                    }
23035                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23036                                        self.write_keyword("TEXT");
23037                                        if let Some(args) = _args_str {
23038                                            self.write(args);
23039                                        }
23040                                    }
23041                                    Some(DialectType::Hive) => {
23042                                        // Hive: NVARCHAR -> STRING
23043                                        self.write_keyword("STRING");
23044                                    }
23045                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23046                                        if _args_str.is_some() {
23047                                            self.write_keyword("VARCHAR");
23048                                            self.write(_args_str.unwrap());
23049                                        } else {
23050                                            self.write_keyword("STRING");
23051                                        }
23052                                    }
23053                                    _ => {
23054                                        self.write_keyword("VARCHAR");
23055                                        if let Some(args) = _args_str {
23056                                            self.write(args);
23057                                        }
23058                                    }
23059                                }
23060                            }
23061                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23062                            "NCHAR"
23063                                if !matches!(
23064                                    self.config.dialect,
23065                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23066                                ) =>
23067                            {
23068                                match self.config.dialect {
23069                                    Some(DialectType::Oracle) => {
23070                                        // Oracle natively supports NCHAR
23071                                        self.write_keyword("NCHAR");
23072                                        if let Some(args) = _args_str {
23073                                            self.write(args);
23074                                        }
23075                                    }
23076                                    Some(DialectType::BigQuery) => {
23077                                        // BigQuery: NCHAR -> STRING
23078                                        self.write_keyword("STRING");
23079                                    }
23080                                    Some(DialectType::Hive) => {
23081                                        // Hive: NCHAR -> STRING
23082                                        self.write_keyword("STRING");
23083                                    }
23084                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23085                                        self.write_keyword("TEXT");
23086                                        if let Some(args) = _args_str {
23087                                            self.write(args);
23088                                        }
23089                                    }
23090                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23091                                        if _args_str.is_some() {
23092                                            self.write_keyword("CHAR");
23093                                            self.write(_args_str.unwrap());
23094                                        } else {
23095                                            self.write_keyword("STRING");
23096                                        }
23097                                    }
23098                                    _ => {
23099                                        self.write_keyword("CHAR");
23100                                        if let Some(args) = _args_str {
23101                                            self.write(args);
23102                                        }
23103                                    }
23104                                }
23105                            }
23106                            // MySQL text variant types -> map to appropriate target type
23107                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23108                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23109                                Some(DialectType::MySQL)
23110                                | Some(DialectType::SingleStore)
23111                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23112                                Some(DialectType::Spark)
23113                                | Some(DialectType::Databricks)
23114                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23115                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23116                                Some(DialectType::Presto)
23117                                | Some(DialectType::Trino)
23118                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23119                                Some(DialectType::Snowflake)
23120                                | Some(DialectType::Redshift)
23121                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23122                                _ => self.write_keyword("TEXT"),
23123                            },
23124                            // MySQL blob variant types -> map to appropriate target type
23125                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23126                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23127                                Some(DialectType::MySQL)
23128                                | Some(DialectType::SingleStore)
23129                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23130                                Some(DialectType::Spark)
23131                                | Some(DialectType::Databricks)
23132                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23133                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23134                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23135                                Some(DialectType::Presto)
23136                                | Some(DialectType::Trino)
23137                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23138                                Some(DialectType::Snowflake)
23139                                | Some(DialectType::Redshift)
23140                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23141                                _ => self.write_keyword("BLOB"),
23142                            },
23143                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23144                            "LONGVARCHAR" => match self.config.dialect {
23145                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23146                                _ => self.write_keyword("VARCHAR"),
23147                            },
23148                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23149                            "DATETIME" => {
23150                                match self.config.dialect {
23151                                    Some(DialectType::MySQL)
23152                                    | Some(DialectType::Doris)
23153                                    | Some(DialectType::StarRocks)
23154                                    | Some(DialectType::TSQL)
23155                                    | Some(DialectType::Fabric)
23156                                    | Some(DialectType::BigQuery)
23157                                    | Some(DialectType::SQLite)
23158                                    | Some(DialectType::Snowflake) => {
23159                                        self.write_keyword("DATETIME");
23160                                        if let Some(args) = _args_str {
23161                                            self.write(args);
23162                                        }
23163                                    }
23164                                    Some(_) => {
23165                                        // Only map to TIMESTAMP when we have a specific target dialect
23166                                        self.write_keyword("TIMESTAMP");
23167                                        if let Some(args) = _args_str {
23168                                            self.write(args);
23169                                        }
23170                                    }
23171                                    None => {
23172                                        // No dialect - preserve original
23173                                        self.write(name);
23174                                    }
23175                                }
23176                            }
23177                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23178                            "VARCHAR2"
23179                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23180                            {
23181                                match self.config.dialect {
23182                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23183                                        self.write_keyword("TEXT");
23184                                    }
23185                                    Some(DialectType::Hive)
23186                                    | Some(DialectType::Spark)
23187                                    | Some(DialectType::Databricks)
23188                                    | Some(DialectType::BigQuery)
23189                                    | Some(DialectType::ClickHouse)
23190                                    | Some(DialectType::StarRocks)
23191                                    | Some(DialectType::Doris) => {
23192                                        self.write_keyword("STRING");
23193                                    }
23194                                    _ => {
23195                                        self.write_keyword("VARCHAR");
23196                                        if let Some(args) = _args_str {
23197                                            self.write(args);
23198                                        }
23199                                    }
23200                                }
23201                            }
23202                            "NVARCHAR2"
23203                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23204                            {
23205                                match self.config.dialect {
23206                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23207                                        self.write_keyword("TEXT");
23208                                    }
23209                                    Some(DialectType::Hive)
23210                                    | Some(DialectType::Spark)
23211                                    | Some(DialectType::Databricks)
23212                                    | Some(DialectType::BigQuery)
23213                                    | Some(DialectType::ClickHouse)
23214                                    | Some(DialectType::StarRocks)
23215                                    | Some(DialectType::Doris) => {
23216                                        self.write_keyword("STRING");
23217                                    }
23218                                    _ => {
23219                                        self.write_keyword("VARCHAR");
23220                                        if let Some(args) = _args_str {
23221                                            self.write(args);
23222                                        }
23223                                    }
23224                                }
23225                            }
23226                            _ => self.write(name),
23227                        }
23228                    }
23229                }
23230            }
23231            DataType::Geometry { subtype, srid } => {
23232                // Dialect-specific geometry type mappings
23233                match self.config.dialect {
23234                    Some(DialectType::MySQL) => {
23235                        // MySQL uses POINT SRID 4326 syntax for specific types
23236                        if let Some(sub) = subtype {
23237                            self.write_keyword(sub);
23238                            if let Some(s) = srid {
23239                                self.write(" SRID ");
23240                                self.write(&s.to_string());
23241                            }
23242                        } else {
23243                            self.write_keyword("GEOMETRY");
23244                        }
23245                    }
23246                    Some(DialectType::BigQuery) => {
23247                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23248                        self.write_keyword("GEOGRAPHY");
23249                    }
23250                    Some(DialectType::Teradata) => {
23251                        // Teradata uses ST_GEOMETRY
23252                        self.write_keyword("ST_GEOMETRY");
23253                        if subtype.is_some() || srid.is_some() {
23254                            self.write("(");
23255                            if let Some(sub) = subtype {
23256                                self.write_keyword(sub);
23257                            }
23258                            if let Some(s) = srid {
23259                                if subtype.is_some() {
23260                                    self.write(", ");
23261                                }
23262                                self.write(&s.to_string());
23263                            }
23264                            self.write(")");
23265                        }
23266                    }
23267                    _ => {
23268                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23269                        self.write_keyword("GEOMETRY");
23270                        if subtype.is_some() || srid.is_some() {
23271                            self.write("(");
23272                            if let Some(sub) = subtype {
23273                                self.write_keyword(sub);
23274                            }
23275                            if let Some(s) = srid {
23276                                if subtype.is_some() {
23277                                    self.write(", ");
23278                                }
23279                                self.write(&s.to_string());
23280                            }
23281                            self.write(")");
23282                        }
23283                    }
23284                }
23285            }
23286            DataType::Geography { subtype, srid } => {
23287                // Dialect-specific geography type mappings
23288                match self.config.dialect {
23289                    Some(DialectType::MySQL) => {
23290                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23291                        if let Some(sub) = subtype {
23292                            self.write_keyword(sub);
23293                        } else {
23294                            self.write_keyword("GEOMETRY");
23295                        }
23296                        // Geography implies SRID 4326 (WGS84)
23297                        let effective_srid = srid.unwrap_or(4326);
23298                        self.write(" SRID ");
23299                        self.write(&effective_srid.to_string());
23300                    }
23301                    Some(DialectType::BigQuery) => {
23302                        // BigQuery uses simple GEOGRAPHY without parameters
23303                        self.write_keyword("GEOGRAPHY");
23304                    }
23305                    Some(DialectType::Snowflake) => {
23306                        // Snowflake uses GEOGRAPHY without parameters
23307                        self.write_keyword("GEOGRAPHY");
23308                    }
23309                    _ => {
23310                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23311                        self.write_keyword("GEOGRAPHY");
23312                        if subtype.is_some() || srid.is_some() {
23313                            self.write("(");
23314                            if let Some(sub) = subtype {
23315                                self.write_keyword(sub);
23316                            }
23317                            if let Some(s) = srid {
23318                                if subtype.is_some() {
23319                                    self.write(", ");
23320                                }
23321                                self.write(&s.to_string());
23322                            }
23323                            self.write(")");
23324                        }
23325                    }
23326                }
23327            }
23328            DataType::CharacterSet { name } => {
23329                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23330                self.write_keyword("CHAR CHARACTER SET ");
23331                self.write(name);
23332            }
23333            _ => self.write("UNKNOWN"),
23334        }
23335        Ok(())
23336    }
23337
23338    // === Helper methods ===
23339
23340    fn write(&mut self, s: &str) {
23341        self.output.push_str(s);
23342    }
23343
23344    fn write_space(&mut self) {
23345        self.output.push(' ');
23346    }
23347
23348    fn write_keyword(&mut self, keyword: &str) {
23349        if self.config.uppercase_keywords {
23350            self.output.push_str(keyword);
23351        } else {
23352            self.output.push_str(&keyword.to_lowercase());
23353        }
23354    }
23355
23356    /// Write a function name respecting the normalize_functions config setting
23357    fn write_func_name(&mut self, name: &str) {
23358        let normalized = self.normalize_func_name(name);
23359        self.output.push_str(&normalized);
23360    }
23361
23362    /// Convert strptime format string to Exasol format string
23363    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23364    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23365    fn convert_strptime_to_exasol_format(format: &str) -> String {
23366        let mut result = String::new();
23367        let chars: Vec<char> = format.chars().collect();
23368        let mut i = 0;
23369        while i < chars.len() {
23370            if chars[i] == '%' && i + 1 < chars.len() {
23371                let spec = chars[i + 1];
23372                let exasol_spec = match spec {
23373                    'Y' => "YYYY",
23374                    'y' => "YY",
23375                    'm' => "MM",
23376                    'd' => "DD",
23377                    'H' => "HH",
23378                    'M' => "MI",
23379                    'S' => "SS",
23380                    'a' => "DY",    // abbreviated weekday name
23381                    'A' => "DAY",   // full weekday name
23382                    'b' => "MON",   // abbreviated month name
23383                    'B' => "MONTH", // full month name
23384                    'I' => "H12",   // 12-hour format
23385                    'u' => "ID",    // ISO weekday (1-7)
23386                    'V' => "IW",    // ISO week number
23387                    'G' => "IYYY",  // ISO year
23388                    'W' => "UW",    // Week number (Monday as first day)
23389                    'U' => "UW",    // Week number (Sunday as first day)
23390                    'z' => "Z",     // timezone offset
23391                    _ => {
23392                        // Unknown specifier, keep as-is
23393                        result.push('%');
23394                        result.push(spec);
23395                        i += 2;
23396                        continue;
23397                    }
23398                };
23399                result.push_str(exasol_spec);
23400                i += 2;
23401            } else {
23402                result.push(chars[i]);
23403                i += 1;
23404            }
23405        }
23406        result
23407    }
23408
23409    /// Convert strptime format string to PostgreSQL/Redshift format string
23410    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23411    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23412    fn convert_strptime_to_postgres_format(format: &str) -> String {
23413        let mut result = String::new();
23414        let chars: Vec<char> = format.chars().collect();
23415        let mut i = 0;
23416        while i < chars.len() {
23417            if chars[i] == '%' && i + 1 < chars.len() {
23418                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23419                if chars[i + 1] == '-' && i + 2 < chars.len() {
23420                    let spec = chars[i + 2];
23421                    let pg_spec = match spec {
23422                        'd' => "FMDD",
23423                        'm' => "FMMM",
23424                        'H' => "FMHH24",
23425                        'M' => "FMMI",
23426                        'S' => "FMSS",
23427                        _ => {
23428                            result.push('%');
23429                            result.push('-');
23430                            result.push(spec);
23431                            i += 3;
23432                            continue;
23433                        }
23434                    };
23435                    result.push_str(pg_spec);
23436                    i += 3;
23437                    continue;
23438                }
23439                let spec = chars[i + 1];
23440                let pg_spec = match spec {
23441                    'Y' => "YYYY",
23442                    'y' => "YY",
23443                    'm' => "MM",
23444                    'd' => "DD",
23445                    'H' => "HH24",
23446                    'I' => "HH12",
23447                    'M' => "MI",
23448                    'S' => "SS",
23449                    'f' => "US",      // microseconds
23450                    'u' => "D",       // day of week (1=Monday)
23451                    'j' => "DDD",     // day of year
23452                    'z' => "OF",      // UTC offset
23453                    'Z' => "TZ",      // timezone name
23454                    'A' => "TMDay",   // full weekday name
23455                    'a' => "TMDy",    // abbreviated weekday name
23456                    'b' => "TMMon",   // abbreviated month name
23457                    'B' => "TMMonth", // full month name
23458                    'U' => "WW",      // week number
23459                    _ => {
23460                        // Unknown specifier, keep as-is
23461                        result.push('%');
23462                        result.push(spec);
23463                        i += 2;
23464                        continue;
23465                    }
23466                };
23467                result.push_str(pg_spec);
23468                i += 2;
23469            } else {
23470                result.push(chars[i]);
23471                i += 1;
23472            }
23473        }
23474        result
23475    }
23476
23477    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23478    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23479        if self.config.limit_only_literals {
23480            if let Some(value) = Self::try_evaluate_constant(expr) {
23481                self.write(&value.to_string());
23482                return Ok(());
23483            }
23484        }
23485        self.generate_expression(expr)
23486    }
23487
23488    /// Format a comment with proper spacing.
23489    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23490    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23491    fn write_formatted_comment(&mut self, comment: &str) {
23492        // Normalize all comments to block comment format /* ... */
23493        // This matches Python sqlglot behavior which always outputs block comments
23494        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23495            // Already block comment - extract inner content
23496            // Preserve internal whitespace, but ensure at least one space padding
23497            &comment[2..comment.len() - 2]
23498        } else if comment.starts_with("--") {
23499            // Line comment - extract content after --
23500            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23501            &comment[2..]
23502        } else {
23503            // Raw content (no delimiters)
23504            comment
23505        };
23506        // Skip empty comments (e.g., bare "--" with no content)
23507        if content.trim().is_empty() {
23508            return;
23509        }
23510        // Ensure at least one space after /* and before */
23511        self.output.push_str("/*");
23512        if !content.starts_with(' ') {
23513            self.output.push(' ');
23514        }
23515        self.output.push_str(content);
23516        if !content.ends_with(' ') {
23517            self.output.push(' ');
23518        }
23519        self.output.push_str("*/");
23520    }
23521
23522    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23523    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23524    fn escape_block_for_single_quote(&self, block: &str) -> String {
23525        let escape_backslash = matches!(
23526            self.config.dialect,
23527            Some(crate::dialects::DialectType::Snowflake)
23528        );
23529        let mut escaped = String::with_capacity(block.len() + 4);
23530        for ch in block.chars() {
23531            if ch == '\'' {
23532                escaped.push('\\');
23533                escaped.push('\'');
23534            } else if escape_backslash && ch == '\\' {
23535                escaped.push('\\');
23536                escaped.push('\\');
23537            } else {
23538                escaped.push(ch);
23539            }
23540        }
23541        escaped
23542    }
23543
23544    fn write_newline(&mut self) {
23545        self.output.push('\n');
23546    }
23547
23548    fn write_indent(&mut self) {
23549        for _ in 0..self.indent_level {
23550            self.output.push_str(&self.config.indent);
23551        }
23552    }
23553
23554    // === SQLGlot-style pretty printing helpers ===
23555
23556    /// Returns the separator string for pretty printing.
23557    /// Check if the total length of arguments exceeds max_text_width.
23558    /// Used for dynamic line breaking in expressions() formatting.
23559    fn too_wide(&self, args: &[String]) -> bool {
23560        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23561    }
23562
23563    /// Generate an expression to a string using a temporary non-pretty generator.
23564    /// Useful for width calculations before deciding on formatting.
23565    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23566        let config = GeneratorConfig {
23567            pretty: false,
23568            dialect: self.config.dialect,
23569            ..Default::default()
23570        };
23571        let mut gen = Generator::with_config(config);
23572        gen.generate_expression(expr)?;
23573        Ok(gen.output)
23574    }
23575
23576    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23577    /// In pretty mode: newline + indented keyword + newline + indented condition
23578    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23579        if self.config.pretty {
23580            self.write_newline();
23581            self.write_indent();
23582            self.write_keyword(keyword);
23583            self.write_newline();
23584            self.indent_level += 1;
23585            self.write_indent();
23586            self.generate_expression(condition)?;
23587            self.indent_level -= 1;
23588        } else {
23589            self.write_space();
23590            self.write_keyword(keyword);
23591            self.write_space();
23592            self.generate_expression(condition)?;
23593        }
23594        Ok(())
23595    }
23596
23597    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23598    /// In pretty mode: each expression on new line with indentation
23599    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23600        if exprs.is_empty() {
23601            return Ok(());
23602        }
23603
23604        if self.config.pretty {
23605            self.write_newline();
23606            self.write_indent();
23607            self.write_keyword(keyword);
23608            self.write_newline();
23609            self.indent_level += 1;
23610            for (i, expr) in exprs.iter().enumerate() {
23611                if i > 0 {
23612                    self.write(",");
23613                    self.write_newline();
23614                }
23615                self.write_indent();
23616                self.generate_expression(expr)?;
23617            }
23618            self.indent_level -= 1;
23619        } else {
23620            self.write_space();
23621            self.write_keyword(keyword);
23622            self.write_space();
23623            for (i, expr) in exprs.iter().enumerate() {
23624                if i > 0 {
23625                    self.write(", ");
23626                }
23627                self.generate_expression(expr)?;
23628            }
23629        }
23630        Ok(())
23631    }
23632
23633    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23634    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23635        if orderings.is_empty() {
23636            return Ok(());
23637        }
23638
23639        if self.config.pretty {
23640            self.write_newline();
23641            self.write_indent();
23642            self.write_keyword(keyword);
23643            self.write_newline();
23644            self.indent_level += 1;
23645            for (i, ordered) in orderings.iter().enumerate() {
23646                if i > 0 {
23647                    self.write(",");
23648                    self.write_newline();
23649                }
23650                self.write_indent();
23651                self.generate_ordered(ordered)?;
23652            }
23653            self.indent_level -= 1;
23654        } else {
23655            self.write_space();
23656            self.write_keyword(keyword);
23657            self.write_space();
23658            for (i, ordered) in orderings.iter().enumerate() {
23659                if i > 0 {
23660                    self.write(", ");
23661                }
23662                self.generate_ordered(ordered)?;
23663            }
23664        }
23665        Ok(())
23666    }
23667
23668    /// Writes WINDOW clause with named window definitions
23669    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23670        if windows.is_empty() {
23671            return Ok(());
23672        }
23673
23674        if self.config.pretty {
23675            self.write_newline();
23676            self.write_indent();
23677            self.write_keyword("WINDOW");
23678            self.write_newline();
23679            self.indent_level += 1;
23680            for (i, named_window) in windows.iter().enumerate() {
23681                if i > 0 {
23682                    self.write(",");
23683                    self.write_newline();
23684                }
23685                self.write_indent();
23686                self.generate_identifier(&named_window.name)?;
23687                self.write_space();
23688                self.write_keyword("AS");
23689                self.write(" (");
23690                self.generate_over(&named_window.spec)?;
23691                self.write(")");
23692            }
23693            self.indent_level -= 1;
23694        } else {
23695            self.write_space();
23696            self.write_keyword("WINDOW");
23697            self.write_space();
23698            for (i, named_window) in windows.iter().enumerate() {
23699                if i > 0 {
23700                    self.write(", ");
23701                }
23702                self.generate_identifier(&named_window.name)?;
23703                self.write_space();
23704                self.write_keyword("AS");
23705                self.write(" (");
23706                self.generate_over(&named_window.spec)?;
23707                self.write(")");
23708            }
23709        }
23710        Ok(())
23711    }
23712
23713    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23714    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23715        // AI_AGG(this, expression)
23716        self.write_keyword("AI_AGG");
23717        self.write("(");
23718        self.generate_expression(&e.this)?;
23719        self.write(", ");
23720        self.generate_expression(&e.expression)?;
23721        self.write(")");
23722        Ok(())
23723    }
23724
23725    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23726        // AI_CLASSIFY(input, [categories], [config])
23727        self.write_keyword("AI_CLASSIFY");
23728        self.write("(");
23729        self.generate_expression(&e.this)?;
23730        if let Some(categories) = &e.categories {
23731            self.write(", ");
23732            self.generate_expression(categories)?;
23733        }
23734        if let Some(config) = &e.config {
23735            self.write(", ");
23736            self.generate_expression(config)?;
23737        }
23738        self.write(")");
23739        Ok(())
23740    }
23741
23742    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23743        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23744        self.write_keyword("ADD");
23745        self.write_space();
23746        if e.exists {
23747            self.write_keyword("IF NOT EXISTS");
23748            self.write_space();
23749        }
23750        self.generate_expression(&e.this)?;
23751        if let Some(location) = &e.location {
23752            self.write_space();
23753            self.generate_expression(location)?;
23754        }
23755        Ok(())
23756    }
23757
23758    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23759        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23760        self.write_keyword("ALGORITHM");
23761        self.write("=");
23762        self.generate_expression(&e.this)?;
23763        Ok(())
23764    }
23765
23766    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23767        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23768        self.generate_expression(&e.this)?;
23769        self.write_space();
23770        self.write_keyword("AS");
23771        self.write(" (");
23772        for (i, expr) in e.expressions.iter().enumerate() {
23773            if i > 0 {
23774                self.write(", ");
23775            }
23776            self.generate_expression(expr)?;
23777        }
23778        self.write(")");
23779        Ok(())
23780    }
23781
23782    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23783        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23784        self.write_keyword("ALLOWED_VALUES");
23785        self.write_space();
23786        for (i, expr) in e.expressions.iter().enumerate() {
23787            if i > 0 {
23788                self.write(", ");
23789            }
23790            self.generate_expression(expr)?;
23791        }
23792        Ok(())
23793    }
23794
23795    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23796        // Python: complex logic based on dtype, default, comment, visible, etc.
23797        self.write_keyword("ALTER COLUMN");
23798        self.write_space();
23799        self.generate_expression(&e.this)?;
23800
23801        if let Some(dtype) = &e.dtype {
23802            self.write_space();
23803            self.write_keyword("SET DATA TYPE");
23804            self.write_space();
23805            self.generate_expression(dtype)?;
23806            if let Some(collate) = &e.collate {
23807                self.write_space();
23808                self.write_keyword("COLLATE");
23809                self.write_space();
23810                self.generate_expression(collate)?;
23811            }
23812            if let Some(using) = &e.using {
23813                self.write_space();
23814                self.write_keyword("USING");
23815                self.write_space();
23816                self.generate_expression(using)?;
23817            }
23818        } else if let Some(default) = &e.default {
23819            self.write_space();
23820            self.write_keyword("SET DEFAULT");
23821            self.write_space();
23822            self.generate_expression(default)?;
23823        } else if let Some(comment) = &e.comment {
23824            self.write_space();
23825            self.write_keyword("COMMENT");
23826            self.write_space();
23827            self.generate_expression(comment)?;
23828        } else if let Some(drop) = &e.drop {
23829            self.write_space();
23830            self.write_keyword("DROP");
23831            self.write_space();
23832            self.generate_expression(drop)?;
23833        } else if let Some(visible) = &e.visible {
23834            self.write_space();
23835            self.generate_expression(visible)?;
23836        } else if let Some(rename_to) = &e.rename_to {
23837            self.write_space();
23838            self.write_keyword("RENAME TO");
23839            self.write_space();
23840            self.generate_expression(rename_to)?;
23841        } else if let Some(allow_null) = &e.allow_null {
23842            self.write_space();
23843            self.generate_expression(allow_null)?;
23844        }
23845        Ok(())
23846    }
23847
23848    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23849        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23850        self.write_keyword("ALTER SESSION");
23851        self.write_space();
23852        if e.unset.is_some() {
23853            self.write_keyword("UNSET");
23854        } else {
23855            self.write_keyword("SET");
23856        }
23857        self.write_space();
23858        for (i, expr) in e.expressions.iter().enumerate() {
23859            if i > 0 {
23860                self.write(", ");
23861            }
23862            self.generate_expression(expr)?;
23863        }
23864        Ok(())
23865    }
23866
23867    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23868        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23869        self.write_keyword("SET");
23870
23871        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23872        if let Some(opt) = &e.option {
23873            self.write_space();
23874            self.generate_expression(opt)?;
23875        }
23876
23877        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23878        // Check if expressions look like property assignments
23879        if !e.expressions.is_empty() {
23880            // Check if this looks like property assignments (for SET PROPERTIES)
23881            let is_properties = e
23882                .expressions
23883                .iter()
23884                .any(|expr| matches!(expr, Expression::Eq(_)));
23885            if is_properties && e.option.is_none() {
23886                self.write_space();
23887                self.write_keyword("PROPERTIES");
23888            }
23889            self.write_space();
23890            for (i, expr) in e.expressions.iter().enumerate() {
23891                if i > 0 {
23892                    self.write(", ");
23893                }
23894                self.generate_expression(expr)?;
23895            }
23896        }
23897
23898        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23899        if let Some(file_format) = &e.file_format {
23900            self.write(" ");
23901            self.write_keyword("STAGE_FILE_FORMAT");
23902            self.write(" = (");
23903            self.generate_space_separated_properties(file_format)?;
23904            self.write(")");
23905        }
23906
23907        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23908        if let Some(copy_options) = &e.copy_options {
23909            self.write(" ");
23910            self.write_keyword("STAGE_COPY_OPTIONS");
23911            self.write(" = (");
23912            self.generate_space_separated_properties(copy_options)?;
23913            self.write(")");
23914        }
23915
23916        // Generate TAG ...
23917        if let Some(tag) = &e.tag {
23918            self.write(" ");
23919            self.write_keyword("TAG");
23920            self.write(" ");
23921            self.generate_expression(tag)?;
23922        }
23923
23924        Ok(())
23925    }
23926
23927    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23928    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23929        match expr {
23930            Expression::Tuple(t) => {
23931                for (i, prop) in t.expressions.iter().enumerate() {
23932                    if i > 0 {
23933                        self.write(" ");
23934                    }
23935                    self.generate_expression(prop)?;
23936                }
23937            }
23938            _ => {
23939                self.generate_expression(expr)?;
23940            }
23941        }
23942        Ok(())
23943    }
23944
23945    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23946        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23947        self.write_keyword("ALTER");
23948        if e.compound.is_some() {
23949            self.write_space();
23950            self.write_keyword("COMPOUND");
23951        }
23952        self.write_space();
23953        self.write_keyword("SORTKEY");
23954        self.write_space();
23955        if let Some(this) = &e.this {
23956            self.generate_expression(this)?;
23957        } else if !e.expressions.is_empty() {
23958            self.write("(");
23959            for (i, expr) in e.expressions.iter().enumerate() {
23960                if i > 0 {
23961                    self.write(", ");
23962                }
23963                self.generate_expression(expr)?;
23964            }
23965            self.write(")");
23966        }
23967        Ok(())
23968    }
23969
23970    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23971        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23972        self.write_keyword("ANALYZE");
23973        if !e.options.is_empty() {
23974            self.write_space();
23975            for (i, opt) in e.options.iter().enumerate() {
23976                if i > 0 {
23977                    self.write_space();
23978                }
23979                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23980                if let Expression::Identifier(id) = opt {
23981                    self.write_keyword(&id.name);
23982                } else {
23983                    self.generate_expression(opt)?;
23984                }
23985            }
23986        }
23987        if let Some(kind) = &e.kind {
23988            self.write_space();
23989            self.write_keyword(kind);
23990        }
23991        if let Some(this) = &e.this {
23992            self.write_space();
23993            self.generate_expression(this)?;
23994        }
23995        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
23996        if !e.columns.is_empty() {
23997            self.write("(");
23998            for (i, col) in e.columns.iter().enumerate() {
23999                if i > 0 {
24000                    self.write(", ");
24001                }
24002                self.write(col);
24003            }
24004            self.write(")");
24005        }
24006        if let Some(partition) = &e.partition {
24007            self.write_space();
24008            self.generate_expression(partition)?;
24009        }
24010        if let Some(mode) = &e.mode {
24011            self.write_space();
24012            self.generate_expression(mode)?;
24013        }
24014        if let Some(expression) = &e.expression {
24015            self.write_space();
24016            self.generate_expression(expression)?;
24017        }
24018        if !e.properties.is_empty() {
24019            self.write_space();
24020            self.write_keyword(self.config.with_properties_prefix);
24021            self.write(" (");
24022            for (i, prop) in e.properties.iter().enumerate() {
24023                if i > 0 {
24024                    self.write(", ");
24025                }
24026                self.generate_expression(prop)?;
24027            }
24028            self.write(")");
24029        }
24030        Ok(())
24031    }
24032
24033    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
24034        // Python: return f"DELETE{kind} STATISTICS"
24035        self.write_keyword("DELETE");
24036        if let Some(kind) = &e.kind {
24037            self.write_space();
24038            self.write_keyword(kind);
24039        }
24040        self.write_space();
24041        self.write_keyword("STATISTICS");
24042        Ok(())
24043    }
24044
24045    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
24046        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
24047        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
24048        if let Expression::Identifier(id) = e.this.as_ref() {
24049            self.write_keyword(&id.name);
24050        } else {
24051            self.generate_expression(&e.this)?;
24052        }
24053        self.write_space();
24054        self.write_keyword("HISTOGRAM ON");
24055        self.write_space();
24056        for (i, expr) in e.expressions.iter().enumerate() {
24057            if i > 0 {
24058                self.write(", ");
24059            }
24060            self.generate_expression(expr)?;
24061        }
24062        if let Some(expression) = &e.expression {
24063            self.write_space();
24064            self.generate_expression(expression)?;
24065        }
24066        if let Some(update_options) = &e.update_options {
24067            self.write_space();
24068            self.generate_expression(update_options)?;
24069            self.write_space();
24070            self.write_keyword("UPDATE");
24071        }
24072        Ok(())
24073    }
24074
24075    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24076        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24077        self.write_keyword("LIST CHAINED ROWS");
24078        if let Some(expression) = &e.expression {
24079            self.write_space();
24080            self.write_keyword("INTO");
24081            self.write_space();
24082            self.generate_expression(expression)?;
24083        }
24084        Ok(())
24085    }
24086
24087    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24088        // Python: return f"SAMPLE {sample} {kind}"
24089        self.write_keyword("SAMPLE");
24090        self.write_space();
24091        if let Some(sample) = &e.sample {
24092            self.generate_expression(sample)?;
24093            self.write_space();
24094        }
24095        self.write_keyword(&e.kind);
24096        Ok(())
24097    }
24098
24099    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24100        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24101        self.write_keyword(&e.kind);
24102        if let Some(option) = &e.option {
24103            self.write_space();
24104            self.generate_expression(option)?;
24105        }
24106        self.write_space();
24107        self.write_keyword("STATISTICS");
24108        if let Some(this) = &e.this {
24109            self.write_space();
24110            self.generate_expression(this)?;
24111        }
24112        if !e.expressions.is_empty() {
24113            self.write_space();
24114            for (i, expr) in e.expressions.iter().enumerate() {
24115                if i > 0 {
24116                    self.write(", ");
24117                }
24118                self.generate_expression(expr)?;
24119            }
24120        }
24121        Ok(())
24122    }
24123
24124    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24125        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24126        self.write_keyword("VALIDATE");
24127        self.write_space();
24128        self.write_keyword(&e.kind);
24129        if let Some(this) = &e.this {
24130            self.write_space();
24131            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24132            if let Expression::Identifier(id) = this.as_ref() {
24133                self.write_keyword(&id.name);
24134            } else {
24135                self.generate_expression(this)?;
24136            }
24137        }
24138        if let Some(expression) = &e.expression {
24139            self.write_space();
24140            self.write_keyword("INTO");
24141            self.write_space();
24142            self.generate_expression(expression)?;
24143        }
24144        Ok(())
24145    }
24146
24147    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24148        // Python: return f"WITH {expressions}"
24149        self.write_keyword("WITH");
24150        self.write_space();
24151        for (i, expr) in e.expressions.iter().enumerate() {
24152            if i > 0 {
24153                self.write(", ");
24154            }
24155            self.generate_expression(expr)?;
24156        }
24157        Ok(())
24158    }
24159
24160    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24161        // Anonymous represents a generic function call: FUNC_NAME(args...)
24162        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24163        self.generate_expression(&e.this)?;
24164        self.write("(");
24165        for (i, arg) in e.expressions.iter().enumerate() {
24166            if i > 0 {
24167                self.write(", ");
24168            }
24169            self.generate_expression(arg)?;
24170        }
24171        self.write(")");
24172        Ok(())
24173    }
24174
24175    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24176        // Same as Anonymous but for aggregate functions
24177        self.generate_expression(&e.this)?;
24178        self.write("(");
24179        for (i, arg) in e.expressions.iter().enumerate() {
24180            if i > 0 {
24181                self.write(", ");
24182            }
24183            self.generate_expression(arg)?;
24184        }
24185        self.write(")");
24186        Ok(())
24187    }
24188
24189    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24190        // Python: return f"{this} APPLY({expr})"
24191        self.generate_expression(&e.this)?;
24192        self.write_space();
24193        self.write_keyword("APPLY");
24194        self.write("(");
24195        self.generate_expression(&e.expression)?;
24196        self.write(")");
24197        Ok(())
24198    }
24199
24200    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24201        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24202        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24203        self.write("(");
24204        self.generate_expression(&e.this)?;
24205        if let Some(percentile) = &e.percentile {
24206            self.write(", ");
24207            self.generate_expression(percentile)?;
24208        }
24209        self.write(")");
24210        Ok(())
24211    }
24212
24213    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24214        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24215        self.write_keyword("APPROX_QUANTILE");
24216        self.write("(");
24217        self.generate_expression(&e.this)?;
24218        if let Some(quantile) = &e.quantile {
24219            self.write(", ");
24220            self.generate_expression(quantile)?;
24221        }
24222        if let Some(accuracy) = &e.accuracy {
24223            self.write(", ");
24224            self.generate_expression(accuracy)?;
24225        }
24226        if let Some(weight) = &e.weight {
24227            self.write(", ");
24228            self.generate_expression(weight)?;
24229        }
24230        self.write(")");
24231        Ok(())
24232    }
24233
24234    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24235        // APPROX_QUANTILES(this, expression)
24236        self.write_keyword("APPROX_QUANTILES");
24237        self.write("(");
24238        self.generate_expression(&e.this)?;
24239        if let Some(expression) = &e.expression {
24240            self.write(", ");
24241            self.generate_expression(expression)?;
24242        }
24243        self.write(")");
24244        Ok(())
24245    }
24246
24247    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24248        // APPROX_TOP_K(this[, expression][, counters])
24249        self.write_keyword("APPROX_TOP_K");
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        if let Some(counters) = &e.counters {
24257            self.write(", ");
24258            self.generate_expression(counters)?;
24259        }
24260        self.write(")");
24261        Ok(())
24262    }
24263
24264    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24265        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24266        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24267        self.write("(");
24268        self.generate_expression(&e.this)?;
24269        if let Some(expression) = &e.expression {
24270            self.write(", ");
24271            self.generate_expression(expression)?;
24272        }
24273        self.write(")");
24274        Ok(())
24275    }
24276
24277    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24278        // APPROX_TOP_K_COMBINE(this[, expression])
24279        self.write_keyword("APPROX_TOP_K_COMBINE");
24280        self.write("(");
24281        self.generate_expression(&e.this)?;
24282        if let Some(expression) = &e.expression {
24283            self.write(", ");
24284            self.generate_expression(expression)?;
24285        }
24286        self.write(")");
24287        Ok(())
24288    }
24289
24290    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24291        // APPROX_TOP_K_ESTIMATE(this[, expression])
24292        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24293        self.write("(");
24294        self.generate_expression(&e.this)?;
24295        if let Some(expression) = &e.expression {
24296            self.write(", ");
24297            self.generate_expression(expression)?;
24298        }
24299        self.write(")");
24300        Ok(())
24301    }
24302
24303    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24304        // APPROX_TOP_SUM(this, expression[, count])
24305        self.write_keyword("APPROX_TOP_SUM");
24306        self.write("(");
24307        self.generate_expression(&e.this)?;
24308        self.write(", ");
24309        self.generate_expression(&e.expression)?;
24310        if let Some(count) = &e.count {
24311            self.write(", ");
24312            self.generate_expression(count)?;
24313        }
24314        self.write(")");
24315        Ok(())
24316    }
24317
24318    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24319        // ARG_MAX(this, expression[, count])
24320        self.write_keyword("ARG_MAX");
24321        self.write("(");
24322        self.generate_expression(&e.this)?;
24323        self.write(", ");
24324        self.generate_expression(&e.expression)?;
24325        if let Some(count) = &e.count {
24326            self.write(", ");
24327            self.generate_expression(count)?;
24328        }
24329        self.write(")");
24330        Ok(())
24331    }
24332
24333    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24334        // ARG_MIN(this, expression[, count])
24335        self.write_keyword("ARG_MIN");
24336        self.write("(");
24337        self.generate_expression(&e.this)?;
24338        self.write(", ");
24339        self.generate_expression(&e.expression)?;
24340        if let Some(count) = &e.count {
24341            self.write(", ");
24342            self.generate_expression(count)?;
24343        }
24344        self.write(")");
24345        Ok(())
24346    }
24347
24348    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24349        // ARRAY_ALL(this, expression)
24350        self.write_keyword("ARRAY_ALL");
24351        self.write("(");
24352        self.generate_expression(&e.this)?;
24353        self.write(", ");
24354        self.generate_expression(&e.expression)?;
24355        self.write(")");
24356        Ok(())
24357    }
24358
24359    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24360        // ARRAY_ANY(this, expression) - fallback implementation
24361        self.write_keyword("ARRAY_ANY");
24362        self.write("(");
24363        self.generate_expression(&e.this)?;
24364        self.write(", ");
24365        self.generate_expression(&e.expression)?;
24366        self.write(")");
24367        Ok(())
24368    }
24369
24370    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24371        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24372        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24373        self.write("(");
24374        for (i, expr) in e.expressions.iter().enumerate() {
24375            if i > 0 {
24376                self.write(", ");
24377            }
24378            self.generate_expression(expr)?;
24379        }
24380        self.write(")");
24381        Ok(())
24382    }
24383
24384    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24385        // ARRAY_SUM(this[, expression])
24386        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24387            self.write("arraySum");
24388        } else {
24389            self.write_keyword("ARRAY_SUM");
24390        }
24391        self.write("(");
24392        self.generate_expression(&e.this)?;
24393        if let Some(expression) = &e.expression {
24394            self.write(", ");
24395            self.generate_expression(expression)?;
24396        }
24397        self.write(")");
24398        Ok(())
24399    }
24400
24401    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24402        // Python: return f"{this} AT {index}"
24403        self.generate_expression(&e.this)?;
24404        self.write_space();
24405        self.write_keyword("AT");
24406        self.write_space();
24407        self.generate_expression(&e.expression)?;
24408        Ok(())
24409    }
24410
24411    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24412        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24413        self.write_keyword("ATTACH");
24414        if e.exists {
24415            self.write_space();
24416            self.write_keyword("IF NOT EXISTS");
24417        }
24418        self.write_space();
24419        self.generate_expression(&e.this)?;
24420        if !e.expressions.is_empty() {
24421            self.write(" (");
24422            for (i, expr) in e.expressions.iter().enumerate() {
24423                if i > 0 {
24424                    self.write(", ");
24425                }
24426                self.generate_expression(expr)?;
24427            }
24428            self.write(")");
24429        }
24430        Ok(())
24431    }
24432
24433    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24434        // AttachOption: this [expression]
24435        // Python sqlglot: no equals sign, just space-separated
24436        self.generate_expression(&e.this)?;
24437        if let Some(expression) = &e.expression {
24438            self.write_space();
24439            self.generate_expression(expression)?;
24440        }
24441        Ok(())
24442    }
24443
24444    /// Generate the auto_increment keyword and options for a column definition.
24445    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24446    /// GENERATED AS IDENTITY, etc.
24447    fn generate_auto_increment_keyword(
24448        &mut self,
24449        col: &crate::expressions::ColumnDef,
24450    ) -> Result<()> {
24451        use crate::dialects::DialectType;
24452        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24453            self.write_keyword("IDENTITY");
24454            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24455                self.write("(");
24456                if let Some(ref start) = col.auto_increment_start {
24457                    self.generate_expression(start)?;
24458                } else {
24459                    self.write("0");
24460                }
24461                self.write(", ");
24462                if let Some(ref inc) = col.auto_increment_increment {
24463                    self.generate_expression(inc)?;
24464                } else {
24465                    self.write("1");
24466                }
24467                self.write(")");
24468            }
24469        } else if matches!(
24470            self.config.dialect,
24471            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24472        ) {
24473            self.write_keyword("AUTOINCREMENT");
24474            if let Some(ref start) = col.auto_increment_start {
24475                self.write_space();
24476                self.write_keyword("START");
24477                self.write_space();
24478                self.generate_expression(start)?;
24479            }
24480            if let Some(ref inc) = col.auto_increment_increment {
24481                self.write_space();
24482                self.write_keyword("INCREMENT");
24483                self.write_space();
24484                self.generate_expression(inc)?;
24485            }
24486            if let Some(order) = col.auto_increment_order {
24487                self.write_space();
24488                if order {
24489                    self.write_keyword("ORDER");
24490                } else {
24491                    self.write_keyword("NOORDER");
24492                }
24493            }
24494        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24495            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24496            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24497                self.write(" (");
24498                let mut first = true;
24499                if let Some(ref start) = col.auto_increment_start {
24500                    self.write_keyword("START WITH");
24501                    self.write_space();
24502                    self.generate_expression(start)?;
24503                    first = false;
24504                }
24505                if let Some(ref inc) = col.auto_increment_increment {
24506                    if !first {
24507                        self.write_space();
24508                    }
24509                    self.write_keyword("INCREMENT BY");
24510                    self.write_space();
24511                    self.generate_expression(inc)?;
24512                }
24513                self.write(")");
24514            }
24515        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24516            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24517            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24518                self.write(" (");
24519                let mut first = true;
24520                if let Some(ref start) = col.auto_increment_start {
24521                    self.write_keyword("START WITH");
24522                    self.write_space();
24523                    self.generate_expression(start)?;
24524                    first = false;
24525                }
24526                if let Some(ref inc) = col.auto_increment_increment {
24527                    if !first {
24528                        self.write_space();
24529                    }
24530                    self.write_keyword("INCREMENT BY");
24531                    self.write_space();
24532                    self.generate_expression(inc)?;
24533                }
24534                self.write(")");
24535            }
24536        } else if matches!(
24537            self.config.dialect,
24538            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24539        ) {
24540            self.write_keyword("IDENTITY");
24541            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24542                self.write("(");
24543                if let Some(ref start) = col.auto_increment_start {
24544                    self.generate_expression(start)?;
24545                } else {
24546                    self.write("0");
24547                }
24548                self.write(", ");
24549                if let Some(ref inc) = col.auto_increment_increment {
24550                    self.generate_expression(inc)?;
24551                } else {
24552                    self.write("1");
24553                }
24554                self.write(")");
24555            }
24556        } else {
24557            self.write_keyword("AUTO_INCREMENT");
24558            if let Some(ref start) = col.auto_increment_start {
24559                self.write_space();
24560                self.write_keyword("START");
24561                self.write_space();
24562                self.generate_expression(start)?;
24563            }
24564            if let Some(ref inc) = col.auto_increment_increment {
24565                self.write_space();
24566                self.write_keyword("INCREMENT");
24567                self.write_space();
24568                self.generate_expression(inc)?;
24569            }
24570            if let Some(order) = col.auto_increment_order {
24571                self.write_space();
24572                if order {
24573                    self.write_keyword("ORDER");
24574                } else {
24575                    self.write_keyword("NOORDER");
24576                }
24577            }
24578        }
24579        Ok(())
24580    }
24581
24582    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24583        // AUTO_INCREMENT=value
24584        self.write_keyword("AUTO_INCREMENT");
24585        self.write("=");
24586        self.generate_expression(&e.this)?;
24587        Ok(())
24588    }
24589
24590    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24591        // AUTO_REFRESH=value
24592        self.write_keyword("AUTO_REFRESH");
24593        self.write("=");
24594        self.generate_expression(&e.this)?;
24595        Ok(())
24596    }
24597
24598    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24599        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24600        self.write_keyword("BACKUP");
24601        self.write_space();
24602        self.generate_expression(&e.this)?;
24603        Ok(())
24604    }
24605
24606    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24607        // BASE64_DECODE_BINARY(this[, alphabet])
24608        self.write_keyword("BASE64_DECODE_BINARY");
24609        self.write("(");
24610        self.generate_expression(&e.this)?;
24611        if let Some(alphabet) = &e.alphabet {
24612            self.write(", ");
24613            self.generate_expression(alphabet)?;
24614        }
24615        self.write(")");
24616        Ok(())
24617    }
24618
24619    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24620        // BASE64_DECODE_STRING(this[, alphabet])
24621        self.write_keyword("BASE64_DECODE_STRING");
24622        self.write("(");
24623        self.generate_expression(&e.this)?;
24624        if let Some(alphabet) = &e.alphabet {
24625            self.write(", ");
24626            self.generate_expression(alphabet)?;
24627        }
24628        self.write(")");
24629        Ok(())
24630    }
24631
24632    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24633        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24634        self.write_keyword("BASE64_ENCODE");
24635        self.write("(");
24636        self.generate_expression(&e.this)?;
24637        if let Some(max_line_length) = &e.max_line_length {
24638            self.write(", ");
24639            self.generate_expression(max_line_length)?;
24640        }
24641        if let Some(alphabet) = &e.alphabet {
24642            self.write(", ");
24643            self.generate_expression(alphabet)?;
24644        }
24645        self.write(")");
24646        Ok(())
24647    }
24648
24649    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24650        // BLOCKCOMPRESSION=... (complex Teradata property)
24651        self.write_keyword("BLOCKCOMPRESSION");
24652        self.write("=");
24653        if let Some(autotemp) = &e.autotemp {
24654            self.write_keyword("AUTOTEMP");
24655            self.write("(");
24656            self.generate_expression(autotemp)?;
24657            self.write(")");
24658        }
24659        if let Some(always) = &e.always {
24660            self.generate_expression(always)?;
24661        }
24662        if let Some(default) = &e.default {
24663            self.generate_expression(default)?;
24664        }
24665        if let Some(manual) = &e.manual {
24666            self.generate_expression(manual)?;
24667        }
24668        if let Some(never) = &e.never {
24669            self.generate_expression(never)?;
24670        }
24671        Ok(())
24672    }
24673
24674    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24675        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24676        self.write("((");
24677        self.generate_expression(&e.this)?;
24678        self.write(") ");
24679        self.write_keyword("AND");
24680        self.write(" (");
24681        self.generate_expression(&e.expression)?;
24682        self.write("))");
24683        Ok(())
24684    }
24685
24686    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24687        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24688        self.write("((");
24689        self.generate_expression(&e.this)?;
24690        self.write(") ");
24691        self.write_keyword("OR");
24692        self.write(" (");
24693        self.generate_expression(&e.expression)?;
24694        self.write("))");
24695        Ok(())
24696    }
24697
24698    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24699        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24700        self.write_keyword("BUILD");
24701        self.write_space();
24702        self.generate_expression(&e.this)?;
24703        Ok(())
24704    }
24705
24706    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24707        // Byte string literal like B'...' or X'...'
24708        self.generate_expression(&e.this)?;
24709        Ok(())
24710    }
24711
24712    fn generate_case_specific_column_constraint(
24713        &mut self,
24714        e: &CaseSpecificColumnConstraint,
24715    ) -> Result<()> {
24716        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24717        if e.not_.is_some() {
24718            self.write_keyword("NOT");
24719            self.write_space();
24720        }
24721        self.write_keyword("CASESPECIFIC");
24722        Ok(())
24723    }
24724
24725    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24726        // Cast to string type (dialect-specific)
24727        self.write_keyword("CAST");
24728        self.write("(");
24729        self.generate_expression(&e.this)?;
24730        if self.config.dialect == Some(DialectType::ClickHouse) {
24731            // ClickHouse: CAST(expr, 'type_string')
24732            self.write(", ");
24733        } else {
24734            self.write_space();
24735            self.write_keyword("AS");
24736            self.write_space();
24737        }
24738        if let Some(to) = &e.to {
24739            self.generate_expression(to)?;
24740        }
24741        self.write(")");
24742        Ok(())
24743    }
24744
24745    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24746        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24747        // Python: f"CHANGES ({information}){at_before}{end}"
24748        self.write_keyword("CHANGES");
24749        self.write(" (");
24750        if let Some(information) = &e.information {
24751            self.write_keyword("INFORMATION");
24752            self.write(" => ");
24753            self.generate_expression(information)?;
24754        }
24755        self.write(")");
24756        // at_before and end are HistoricalData expressions that generate their own keywords
24757        if let Some(at_before) = &e.at_before {
24758            self.write(" ");
24759            self.generate_expression(at_before)?;
24760        }
24761        if let Some(end) = &e.end {
24762            self.write(" ");
24763            self.generate_expression(end)?;
24764        }
24765        Ok(())
24766    }
24767
24768    fn generate_character_set_column_constraint(
24769        &mut self,
24770        e: &CharacterSetColumnConstraint,
24771    ) -> Result<()> {
24772        // CHARACTER SET charset_name
24773        self.write_keyword("CHARACTER SET");
24774        self.write_space();
24775        self.generate_expression(&e.this)?;
24776        Ok(())
24777    }
24778
24779    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24780        // [DEFAULT] CHARACTER SET=value
24781        if e.default.is_some() {
24782            self.write_keyword("DEFAULT");
24783            self.write_space();
24784        }
24785        self.write_keyword("CHARACTER SET");
24786        self.write("=");
24787        self.generate_expression(&e.this)?;
24788        Ok(())
24789    }
24790
24791    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24792        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24793        self.write_keyword("CHECK");
24794        self.write(" (");
24795        self.generate_expression(&e.this)?;
24796        self.write(")");
24797        if e.enforced.is_some() {
24798            self.write_space();
24799            self.write_keyword("ENFORCED");
24800        }
24801        Ok(())
24802    }
24803
24804    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24805        // CHECK_JSON(this)
24806        self.write_keyword("CHECK_JSON");
24807        self.write("(");
24808        self.generate_expression(&e.this)?;
24809        self.write(")");
24810        Ok(())
24811    }
24812
24813    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24814        // CHECK_XML(this)
24815        self.write_keyword("CHECK_XML");
24816        self.write("(");
24817        self.generate_expression(&e.this)?;
24818        self.write(")");
24819        Ok(())
24820    }
24821
24822    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24823        // CHECKSUM=[ON|OFF|DEFAULT]
24824        self.write_keyword("CHECKSUM");
24825        self.write("=");
24826        if e.on.is_some() {
24827            self.write_keyword("ON");
24828        } else if e.default.is_some() {
24829            self.write_keyword("DEFAULT");
24830        } else {
24831            self.write_keyword("OFF");
24832        }
24833        Ok(())
24834    }
24835
24836    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24837        // Python: return f"{shallow}{keyword} {this}"
24838        if e.shallow.is_some() {
24839            self.write_keyword("SHALLOW");
24840            self.write_space();
24841        }
24842        if e.copy.is_some() {
24843            self.write_keyword("COPY");
24844        } else {
24845            self.write_keyword("CLONE");
24846        }
24847        self.write_space();
24848        self.generate_expression(&e.this)?;
24849        Ok(())
24850    }
24851
24852    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24853        // CLUSTER BY (expressions)
24854        self.write_keyword("CLUSTER BY");
24855        self.write(" (");
24856        for (i, ord) in e.expressions.iter().enumerate() {
24857            if i > 0 {
24858                self.write(", ");
24859            }
24860            self.generate_ordered(ord)?;
24861        }
24862        self.write(")");
24863        Ok(())
24864    }
24865
24866    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
24867        // BigQuery table property: CLUSTER BY col1, col2
24868        self.write_keyword("CLUSTER BY");
24869        self.write_space();
24870        for (i, col) in e.columns.iter().enumerate() {
24871            if i > 0 {
24872                self.write(", ");
24873            }
24874            self.generate_identifier(col)?;
24875        }
24876        Ok(())
24877    }
24878
24879    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24880        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24881        self.write_keyword("CLUSTERED BY");
24882        self.write(" (");
24883        for (i, expr) in e.expressions.iter().enumerate() {
24884            if i > 0 {
24885                self.write(", ");
24886            }
24887            self.generate_expression(expr)?;
24888        }
24889        self.write(")");
24890        if let Some(sorted_by) = &e.sorted_by {
24891            self.write_space();
24892            self.write_keyword("SORTED BY");
24893            self.write(" (");
24894            // Unwrap Tuple to avoid double parentheses
24895            if let Expression::Tuple(t) = sorted_by.as_ref() {
24896                for (i, expr) in t.expressions.iter().enumerate() {
24897                    if i > 0 {
24898                        self.write(", ");
24899                    }
24900                    self.generate_expression(expr)?;
24901                }
24902            } else {
24903                self.generate_expression(sorted_by)?;
24904            }
24905            self.write(")");
24906        }
24907        if let Some(buckets) = &e.buckets {
24908            self.write_space();
24909            self.write_keyword("INTO");
24910            self.write_space();
24911            self.generate_expression(buckets)?;
24912            self.write_space();
24913            self.write_keyword("BUCKETS");
24914        }
24915        Ok(())
24916    }
24917
24918    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24919        // [DEFAULT] COLLATE [=] value
24920        // BigQuery uses space: DEFAULT COLLATE 'en'
24921        // Others use equals: COLLATE='en'
24922        if e.default.is_some() {
24923            self.write_keyword("DEFAULT");
24924            self.write_space();
24925        }
24926        self.write_keyword("COLLATE");
24927        // BigQuery uses space between COLLATE and value
24928        match self.config.dialect {
24929            Some(DialectType::BigQuery) => self.write_space(),
24930            _ => self.write("="),
24931        }
24932        self.generate_expression(&e.this)?;
24933        Ok(())
24934    }
24935
24936    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24937        // ColumnConstraint is an enum
24938        match e {
24939            ColumnConstraint::NotNull => {
24940                self.write_keyword("NOT NULL");
24941            }
24942            ColumnConstraint::Null => {
24943                self.write_keyword("NULL");
24944            }
24945            ColumnConstraint::Unique => {
24946                self.write_keyword("UNIQUE");
24947            }
24948            ColumnConstraint::PrimaryKey => {
24949                self.write_keyword("PRIMARY KEY");
24950            }
24951            ColumnConstraint::Default(expr) => {
24952                self.write_keyword("DEFAULT");
24953                self.write_space();
24954                self.generate_expression(expr)?;
24955            }
24956            ColumnConstraint::Check(expr) => {
24957                self.write_keyword("CHECK");
24958                self.write(" (");
24959                self.generate_expression(expr)?;
24960                self.write(")");
24961            }
24962            ColumnConstraint::References(fk_ref) => {
24963                if fk_ref.has_foreign_key_keywords {
24964                    self.write_keyword("FOREIGN KEY");
24965                    self.write_space();
24966                }
24967                self.write_keyword("REFERENCES");
24968                self.write_space();
24969                self.generate_table(&fk_ref.table)?;
24970                if !fk_ref.columns.is_empty() {
24971                    self.write(" (");
24972                    for (i, col) in fk_ref.columns.iter().enumerate() {
24973                        if i > 0 {
24974                            self.write(", ");
24975                        }
24976                        self.generate_identifier(col)?;
24977                    }
24978                    self.write(")");
24979                }
24980            }
24981            ColumnConstraint::GeneratedAsIdentity(gen) => {
24982                self.write_keyword("GENERATED");
24983                self.write_space();
24984                if gen.always {
24985                    self.write_keyword("ALWAYS");
24986                } else {
24987                    self.write_keyword("BY DEFAULT");
24988                    if gen.on_null {
24989                        self.write_space();
24990                        self.write_keyword("ON NULL");
24991                    }
24992                }
24993                self.write_space();
24994                self.write_keyword("AS IDENTITY");
24995            }
24996            ColumnConstraint::Collate(collation) => {
24997                self.write_keyword("COLLATE");
24998                self.write_space();
24999                self.generate_identifier(collation)?;
25000            }
25001            ColumnConstraint::Comment(comment) => {
25002                self.write_keyword("COMMENT");
25003                self.write(" '");
25004                self.write(comment);
25005                self.write("'");
25006            }
25007            ColumnConstraint::ComputedColumn(cc) => {
25008                self.generate_computed_column_inline(cc)?;
25009            }
25010            ColumnConstraint::GeneratedAsRow(gar) => {
25011                self.generate_generated_as_row_inline(gar)?;
25012            }
25013            ColumnConstraint::Tags(tags) => {
25014                self.write_keyword("TAG");
25015                self.write(" (");
25016                for (i, expr) in tags.expressions.iter().enumerate() {
25017                    if i > 0 {
25018                        self.write(", ");
25019                    }
25020                    self.generate_expression(expr)?;
25021                }
25022                self.write(")");
25023            }
25024            ColumnConstraint::Path(path_expr) => {
25025                self.write_keyword("PATH");
25026                self.write_space();
25027                self.generate_expression(path_expr)?;
25028            }
25029        }
25030        Ok(())
25031    }
25032
25033    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
25034        // ColumnPosition is an enum
25035        match e {
25036            ColumnPosition::First => {
25037                self.write_keyword("FIRST");
25038            }
25039            ColumnPosition::After(ident) => {
25040                self.write_keyword("AFTER");
25041                self.write_space();
25042                self.generate_identifier(ident)?;
25043            }
25044        }
25045        Ok(())
25046    }
25047
25048    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
25049        // column(prefix)
25050        self.generate_expression(&e.this)?;
25051        self.write("(");
25052        self.generate_expression(&e.expression)?;
25053        self.write(")");
25054        Ok(())
25055    }
25056
25057    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
25058        // If unpack is true, this came from * COLUMNS(pattern)
25059        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
25060        if let Some(ref unpack) = e.unpack {
25061            if let Expression::Boolean(b) = unpack.as_ref() {
25062                if b.value {
25063                    self.write("*");
25064                }
25065            }
25066        }
25067        self.write_keyword("COLUMNS");
25068        self.write("(");
25069        self.generate_expression(&e.this)?;
25070        self.write(")");
25071        Ok(())
25072    }
25073
25074    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25075        // Combined aggregate: FUNC(args) combined
25076        self.generate_expression(&e.this)?;
25077        self.write("(");
25078        for (i, expr) in e.expressions.iter().enumerate() {
25079            if i > 0 {
25080                self.write(", ");
25081            }
25082            self.generate_expression(expr)?;
25083        }
25084        self.write(")");
25085        Ok(())
25086    }
25087
25088    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25089        // Combined parameterized aggregate: FUNC(params)(expressions)
25090        self.generate_expression(&e.this)?;
25091        self.write("(");
25092        for (i, param) in e.params.iter().enumerate() {
25093            if i > 0 {
25094                self.write(", ");
25095            }
25096            self.generate_expression(param)?;
25097        }
25098        self.write(")(");
25099        for (i, expr) in e.expressions.iter().enumerate() {
25100            if i > 0 {
25101                self.write(", ");
25102            }
25103            self.generate_expression(expr)?;
25104        }
25105        self.write(")");
25106        Ok(())
25107    }
25108
25109    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25110        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25111        self.write_keyword("COMMIT");
25112
25113        // TSQL always uses COMMIT TRANSACTION
25114        if e.this.is_none()
25115            && matches!(
25116                self.config.dialect,
25117                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25118            )
25119        {
25120            self.write_space();
25121            self.write_keyword("TRANSACTION");
25122        }
25123
25124        // Check if this has TRANSACTION keyword or transaction name
25125        if let Some(this) = &e.this {
25126            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25127            let is_transaction_marker = matches!(
25128                this.as_ref(),
25129                Expression::Identifier(id) if id.name == "TRANSACTION"
25130            );
25131
25132            self.write_space();
25133            self.write_keyword("TRANSACTION");
25134
25135            // If it's a real transaction name, output it
25136            if !is_transaction_marker {
25137                self.write_space();
25138                self.generate_expression(this)?;
25139            }
25140        }
25141
25142        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25143        if let Some(durability) = &e.durability {
25144            self.write_space();
25145            self.write_keyword("WITH");
25146            self.write(" (");
25147            self.write_keyword("DELAYED_DURABILITY");
25148            self.write(" = ");
25149            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25150                self.write_keyword("ON");
25151            } else {
25152                self.write_keyword("OFF");
25153            }
25154            self.write(")");
25155        }
25156
25157        // Output AND [NO] CHAIN
25158        if let Some(chain) = &e.chain {
25159            self.write_space();
25160            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25161                self.write_keyword("AND NO CHAIN");
25162            } else {
25163                self.write_keyword("AND CHAIN");
25164            }
25165        }
25166        Ok(())
25167    }
25168
25169    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25170        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25171        self.write("[");
25172        self.generate_expression(&e.this)?;
25173        self.write_space();
25174        self.write_keyword("FOR");
25175        self.write_space();
25176        self.generate_expression(&e.expression)?;
25177        // Handle optional position variable (for enumerate-like syntax)
25178        if let Some(pos) = &e.position {
25179            self.write(", ");
25180            self.generate_expression(pos)?;
25181        }
25182        if let Some(iterator) = &e.iterator {
25183            self.write_space();
25184            self.write_keyword("IN");
25185            self.write_space();
25186            self.generate_expression(iterator)?;
25187        }
25188        if let Some(condition) = &e.condition {
25189            self.write_space();
25190            self.write_keyword("IF");
25191            self.write_space();
25192            self.generate_expression(condition)?;
25193        }
25194        self.write("]");
25195        Ok(())
25196    }
25197
25198    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25199        // COMPRESS(this[, method])
25200        self.write_keyword("COMPRESS");
25201        self.write("(");
25202        self.generate_expression(&e.this)?;
25203        if let Some(method) = &e.method {
25204            self.write(", '");
25205            self.write(method);
25206            self.write("'");
25207        }
25208        self.write(")");
25209        Ok(())
25210    }
25211
25212    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25213        // Python: return f"COMPRESS {this}"
25214        self.write_keyword("COMPRESS");
25215        if let Some(this) = &e.this {
25216            self.write_space();
25217            self.generate_expression(this)?;
25218        }
25219        Ok(())
25220    }
25221
25222    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25223        // Python: return f"AS {this}{persisted}"
25224        self.write_keyword("AS");
25225        self.write_space();
25226        self.generate_expression(&e.this)?;
25227        if e.not_null.is_some() {
25228            self.write_space();
25229            self.write_keyword("PERSISTED NOT NULL");
25230        } else if e.persisted.is_some() {
25231            self.write_space();
25232            self.write_keyword("PERSISTED");
25233        }
25234        Ok(())
25235    }
25236
25237    /// Generate a ComputedColumn constraint inline within a column definition.
25238    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25239    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25240    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25241        let computed_expr = if matches!(
25242            self.config.dialect,
25243            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25244        ) {
25245            match &*cc.expression {
25246                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25247                {
25248                    let wrapped = Expression::Cast(Box::new(Cast {
25249                        this: y.this.clone(),
25250                        to: DataType::Date,
25251                        trailing_comments: Vec::new(),
25252                        double_colon_syntax: false,
25253                        format: None,
25254                        default: None,
25255                    }));
25256                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25257                }
25258                Expression::Function(f)
25259                    if f.name.eq_ignore_ascii_case("YEAR")
25260                        && f.args.len() == 1
25261                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25262                {
25263                    let wrapped = Expression::Cast(Box::new(Cast {
25264                        this: f.args[0].clone(),
25265                        to: DataType::Date,
25266                        trailing_comments: Vec::new(),
25267                        double_colon_syntax: false,
25268                        format: None,
25269                        default: None,
25270                    }));
25271                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25272                }
25273                _ => *cc.expression.clone(),
25274            }
25275        } else {
25276            *cc.expression.clone()
25277        };
25278
25279        match cc.persistence_kind.as_deref() {
25280            Some("STORED") | Some("VIRTUAL") => {
25281                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25282                self.write_keyword("GENERATED ALWAYS AS");
25283                self.write(" (");
25284                self.generate_expression(&computed_expr)?;
25285                self.write(")");
25286                self.write_space();
25287                if cc.persisted {
25288                    self.write_keyword("STORED");
25289                } else {
25290                    self.write_keyword("VIRTUAL");
25291                }
25292            }
25293            Some("PERSISTED") => {
25294                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25295                self.write_keyword("AS");
25296                self.write(" (");
25297                self.generate_expression(&computed_expr)?;
25298                self.write(")");
25299                self.write_space();
25300                self.write_keyword("PERSISTED");
25301                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25302                if let Some(ref dt) = cc.data_type {
25303                    self.write_space();
25304                    self.generate_data_type(dt)?;
25305                }
25306                if cc.not_null {
25307                    self.write_space();
25308                    self.write_keyword("NOT NULL");
25309                }
25310            }
25311            _ => {
25312                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25313                // TSQL computed column without PERSISTED: AS (expr)
25314                if matches!(
25315                    self.config.dialect,
25316                    Some(DialectType::Spark)
25317                        | Some(DialectType::Databricks)
25318                        | Some(DialectType::Hive)
25319                ) {
25320                    self.write_keyword("GENERATED ALWAYS AS");
25321                    self.write(" (");
25322                    self.generate_expression(&computed_expr)?;
25323                    self.write(")");
25324                } else if matches!(
25325                    self.config.dialect,
25326                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25327                ) {
25328                    self.write_keyword("AS");
25329                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25330                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25331                    if omit_parens {
25332                        self.write_space();
25333                        self.generate_expression(&computed_expr)?;
25334                    } else {
25335                        self.write(" (");
25336                        self.generate_expression(&computed_expr)?;
25337                        self.write(")");
25338                    }
25339                } else {
25340                    self.write_keyword("AS");
25341                    self.write(" (");
25342                    self.generate_expression(&computed_expr)?;
25343                    self.write(")");
25344                }
25345            }
25346        }
25347        Ok(())
25348    }
25349
25350    /// Generate a GeneratedAsRow constraint inline within a column definition.
25351    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25352    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25353        self.write_keyword("GENERATED ALWAYS AS ROW ");
25354        if gar.start {
25355            self.write_keyword("START");
25356        } else {
25357            self.write_keyword("END");
25358        }
25359        if gar.hidden {
25360            self.write_space();
25361            self.write_keyword("HIDDEN");
25362        }
25363        Ok(())
25364    }
25365
25366    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25367    fn generate_system_versioning_content(
25368        &mut self,
25369        e: &WithSystemVersioningProperty,
25370    ) -> Result<()> {
25371        let mut parts = Vec::new();
25372
25373        if let Some(this) = &e.this {
25374            let mut s = String::from("HISTORY_TABLE=");
25375            let mut gen = Generator::new();
25376            gen.config = self.config.clone();
25377            gen.generate_expression(this)?;
25378            s.push_str(&gen.output);
25379            parts.push(s);
25380        }
25381
25382        if let Some(data_consistency) = &e.data_consistency {
25383            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25384            let mut gen = Generator::new();
25385            gen.config = self.config.clone();
25386            gen.generate_expression(data_consistency)?;
25387            s.push_str(&gen.output);
25388            parts.push(s);
25389        }
25390
25391        if let Some(retention_period) = &e.retention_period {
25392            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25393            let mut gen = Generator::new();
25394            gen.config = self.config.clone();
25395            gen.generate_expression(retention_period)?;
25396            s.push_str(&gen.output);
25397            parts.push(s);
25398        }
25399
25400        self.write_keyword("SYSTEM_VERSIONING");
25401        self.write("=");
25402
25403        if !parts.is_empty() {
25404            self.write_keyword("ON");
25405            self.write("(");
25406            self.write(&parts.join(", "));
25407            self.write(")");
25408        } else if e.on.is_some() {
25409            self.write_keyword("ON");
25410        } else {
25411            self.write_keyword("OFF");
25412        }
25413
25414        Ok(())
25415    }
25416
25417    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25418        // Conditional INSERT for multi-table inserts
25419        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25420        if e.else_.is_some() {
25421            self.write_keyword("ELSE");
25422            self.write_space();
25423        } else if let Some(expression) = &e.expression {
25424            self.write_keyword("WHEN");
25425            self.write_space();
25426            self.generate_expression(expression)?;
25427            self.write_space();
25428            self.write_keyword("THEN");
25429            self.write_space();
25430        }
25431
25432        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25433        // without the "INSERT " prefix
25434        if let Expression::Insert(insert) = e.this.as_ref() {
25435            self.write_keyword("INTO");
25436            self.write_space();
25437            self.generate_table(&insert.table)?;
25438
25439            // Optional column list
25440            if !insert.columns.is_empty() {
25441                self.write(" (");
25442                for (i, col) in insert.columns.iter().enumerate() {
25443                    if i > 0 {
25444                        self.write(", ");
25445                    }
25446                    self.generate_identifier(col)?;
25447                }
25448                self.write(")");
25449            }
25450
25451            // Optional VALUES clause
25452            if !insert.values.is_empty() {
25453                self.write_space();
25454                self.write_keyword("VALUES");
25455                for (row_idx, row) in insert.values.iter().enumerate() {
25456                    if row_idx > 0 {
25457                        self.write(", ");
25458                    }
25459                    self.write(" (");
25460                    for (i, val) in row.iter().enumerate() {
25461                        if i > 0 {
25462                            self.write(", ");
25463                        }
25464                        self.generate_expression(val)?;
25465                    }
25466                    self.write(")");
25467                }
25468            }
25469        } else {
25470            // Fallback for non-Insert expressions
25471            self.generate_expression(&e.this)?;
25472        }
25473        Ok(())
25474    }
25475
25476    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25477        // Python: return f"CONSTRAINT {this} {expressions}"
25478        self.write_keyword("CONSTRAINT");
25479        self.write_space();
25480        self.generate_expression(&e.this)?;
25481        if !e.expressions.is_empty() {
25482            self.write_space();
25483            for (i, expr) in e.expressions.iter().enumerate() {
25484                if i > 0 {
25485                    self.write_space();
25486                }
25487                self.generate_expression(expr)?;
25488            }
25489        }
25490        Ok(())
25491    }
25492
25493    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25494        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25495        self.write_keyword("CONVERT_TIMEZONE");
25496        self.write("(");
25497        let mut first = true;
25498        if let Some(source_tz) = &e.source_tz {
25499            self.generate_expression(source_tz)?;
25500            first = false;
25501        }
25502        if let Some(target_tz) = &e.target_tz {
25503            if !first {
25504                self.write(", ");
25505            }
25506            self.generate_expression(target_tz)?;
25507            first = false;
25508        }
25509        if let Some(timestamp) = &e.timestamp {
25510            if !first {
25511                self.write(", ");
25512            }
25513            self.generate_expression(timestamp)?;
25514        }
25515        self.write(")");
25516        Ok(())
25517    }
25518
25519    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25520        // CONVERT(this USING dest)
25521        self.write_keyword("CONVERT");
25522        self.write("(");
25523        self.generate_expression(&e.this)?;
25524        if let Some(dest) = &e.dest {
25525            self.write_space();
25526            self.write_keyword("USING");
25527            self.write_space();
25528            self.generate_expression(dest)?;
25529        }
25530        self.write(")");
25531        Ok(())
25532    }
25533
25534    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25535        self.write_keyword("COPY");
25536        if e.is_into {
25537            self.write_space();
25538            self.write_keyword("INTO");
25539        }
25540        self.write_space();
25541
25542        // Generate target table or query (or stage for COPY INTO @stage)
25543        if let Expression::Literal(Literal::String(s)) = &e.this {
25544            if s.starts_with('@') {
25545                self.write(s);
25546            } else {
25547                self.generate_expression(&e.this)?;
25548            }
25549        } else {
25550            self.generate_expression(&e.this)?;
25551        }
25552
25553        // FROM or TO based on kind
25554        if e.kind {
25555            // kind=true means FROM (loading into table)
25556            if self.config.pretty {
25557                self.write_newline();
25558            } else {
25559                self.write_space();
25560            }
25561            self.write_keyword("FROM");
25562            self.write_space();
25563        } else if !e.files.is_empty() {
25564            // kind=false means TO (exporting)
25565            if self.config.pretty {
25566                self.write_newline();
25567            } else {
25568                self.write_space();
25569            }
25570            self.write_keyword("TO");
25571            self.write_space();
25572        }
25573
25574        // Generate source/destination files
25575        for (i, file) in e.files.iter().enumerate() {
25576            if i > 0 {
25577                self.write_space();
25578            }
25579            // For stage references (strings starting with @), output without quotes
25580            if let Expression::Literal(Literal::String(s)) = file {
25581                if s.starts_with('@') {
25582                    self.write(s);
25583                } else {
25584                    self.generate_expression(file)?;
25585                }
25586            } else if let Expression::Identifier(id) = file {
25587                // Backtick-quoted file path (Databricks style: `s3://link`)
25588                if id.quoted {
25589                    self.write("`");
25590                    self.write(&id.name);
25591                    self.write("`");
25592                } else {
25593                    self.generate_expression(file)?;
25594                }
25595            } else {
25596                self.generate_expression(file)?;
25597            }
25598        }
25599
25600        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25601        if !e.with_wrapped {
25602            if let Some(ref creds) = e.credentials {
25603                if let Some(ref storage) = creds.storage {
25604                    if self.config.pretty {
25605                        self.write_newline();
25606                    } else {
25607                        self.write_space();
25608                    }
25609                    self.write_keyword("STORAGE_INTEGRATION");
25610                    self.write(" = ");
25611                    self.write(storage);
25612                }
25613                if creds.credentials.is_empty() {
25614                    // Empty credentials: CREDENTIALS = ()
25615                    if self.config.pretty {
25616                        self.write_newline();
25617                    } else {
25618                        self.write_space();
25619                    }
25620                    self.write_keyword("CREDENTIALS");
25621                    self.write(" = ()");
25622                } else {
25623                    if self.config.pretty {
25624                        self.write_newline();
25625                    } else {
25626                        self.write_space();
25627                    }
25628                    self.write_keyword("CREDENTIALS");
25629                    // Check if this is Redshift-style (single value with empty key)
25630                    // vs Snowflake-style (multiple key=value pairs)
25631                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25632                        // Redshift style: CREDENTIALS 'value'
25633                        self.write(" '");
25634                        self.write(&creds.credentials[0].1);
25635                        self.write("'");
25636                    } else {
25637                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25638                        self.write(" = (");
25639                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25640                            if i > 0 {
25641                                self.write_space();
25642                            }
25643                            self.write(k);
25644                            self.write("='");
25645                            self.write(v);
25646                            self.write("'");
25647                        }
25648                        self.write(")");
25649                    }
25650                }
25651                if let Some(ref encryption) = creds.encryption {
25652                    self.write_space();
25653                    self.write_keyword("ENCRYPTION");
25654                    self.write(" = ");
25655                    self.write(encryption);
25656                }
25657            }
25658        }
25659
25660        // Generate parameters
25661        if !e.params.is_empty() {
25662            if e.with_wrapped {
25663                // DuckDB/PostgreSQL/TSQL WITH (...) format
25664                self.write_space();
25665                self.write_keyword("WITH");
25666                self.write(" (");
25667                for (i, param) in e.params.iter().enumerate() {
25668                    if i > 0 {
25669                        self.write(", ");
25670                    }
25671                    self.generate_copy_param_with_format(param)?;
25672                }
25673                self.write(")");
25674            } else {
25675                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25676                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25677                // For Snowflake: KEY = VALUE
25678                for param in &e.params {
25679                    if self.config.pretty {
25680                        self.write_newline();
25681                    } else {
25682                        self.write_space();
25683                    }
25684                    // Preserve original case of parameter name (important for Redshift COPY options)
25685                    self.write(&param.name);
25686                    if let Some(ref value) = param.value {
25687                        // Use = only if it was present in the original (param.eq)
25688                        if param.eq {
25689                            self.write(" = ");
25690                        } else {
25691                            self.write(" ");
25692                        }
25693                        if !param.values.is_empty() {
25694                            self.write("(");
25695                            for (i, v) in param.values.iter().enumerate() {
25696                                if i > 0 {
25697                                    self.write_space();
25698                                }
25699                                self.generate_copy_nested_param(v)?;
25700                            }
25701                            self.write(")");
25702                        } else {
25703                            // For COPY parameter values, output identifiers without quoting
25704                            self.generate_copy_param_value(value)?;
25705                        }
25706                    } else if !param.values.is_empty() {
25707                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25708                        if param.eq {
25709                            self.write(" = (");
25710                        } else {
25711                            self.write(" (");
25712                        }
25713                        // Determine separator for values inside parentheses:
25714                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25715                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25716                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25717                        let is_key_value_pairs = param
25718                            .values
25719                            .first()
25720                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25721                        let sep = if is_key_value_pairs && param.eq {
25722                            " "
25723                        } else {
25724                            ", "
25725                        };
25726                        for (i, v) in param.values.iter().enumerate() {
25727                            if i > 0 {
25728                                self.write(sep);
25729                            }
25730                            self.generate_copy_nested_param(v)?;
25731                        }
25732                        self.write(")");
25733                    }
25734                }
25735            }
25736        }
25737
25738        Ok(())
25739    }
25740
25741    /// Generate a COPY parameter in WITH (...) format
25742    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25743    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25744        self.write_keyword(&param.name);
25745        if !param.values.is_empty() {
25746            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25747            self.write(" = (");
25748            for (i, v) in param.values.iter().enumerate() {
25749                if i > 0 {
25750                    self.write(", ");
25751                }
25752                self.generate_copy_nested_param(v)?;
25753            }
25754            self.write(")");
25755        } else if let Some(ref value) = param.value {
25756            if param.eq {
25757                self.write(" = ");
25758            } else {
25759                self.write(" ");
25760            }
25761            self.generate_expression(value)?;
25762        }
25763        Ok(())
25764    }
25765
25766    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25767    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25768        match expr {
25769            Expression::Eq(eq) => {
25770                // Generate key
25771                match &eq.left {
25772                    Expression::Column(c) => self.write(&c.name.name),
25773                    _ => self.generate_expression(&eq.left)?,
25774                }
25775                self.write("=");
25776                // Generate value
25777                match &eq.right {
25778                    Expression::Literal(Literal::String(s)) => {
25779                        self.write("'");
25780                        self.write(s);
25781                        self.write("'");
25782                    }
25783                    Expression::Tuple(t) => {
25784                        // For lists like NULL_IF=('', 'str1')
25785                        self.write("(");
25786                        if self.config.pretty {
25787                            self.write_newline();
25788                            self.indent_level += 1;
25789                            for (i, item) in t.expressions.iter().enumerate() {
25790                                if i > 0 {
25791                                    self.write(", ");
25792                                }
25793                                self.write_indent();
25794                                self.generate_expression(item)?;
25795                            }
25796                            self.write_newline();
25797                            self.indent_level -= 1;
25798                        } else {
25799                            for (i, item) in t.expressions.iter().enumerate() {
25800                                if i > 0 {
25801                                    self.write(", ");
25802                                }
25803                                self.generate_expression(item)?;
25804                            }
25805                        }
25806                        self.write(")");
25807                    }
25808                    _ => self.generate_expression(&eq.right)?,
25809                }
25810                Ok(())
25811            }
25812            Expression::Column(c) => {
25813                // Standalone keyword like COMPRESSION
25814                self.write(&c.name.name);
25815                Ok(())
25816            }
25817            _ => self.generate_expression(expr),
25818        }
25819    }
25820
25821    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25822    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25823    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25824        match expr {
25825            Expression::Column(c) => {
25826                // Output identifier, preserving quotes if originally quoted
25827                if c.name.quoted {
25828                    self.write("\"");
25829                    self.write(&c.name.name);
25830                    self.write("\"");
25831                } else {
25832                    self.write(&c.name.name);
25833                }
25834                Ok(())
25835            }
25836            Expression::Identifier(id) => {
25837                // Output identifier, preserving quotes if originally quoted
25838                if id.quoted {
25839                    self.write("\"");
25840                    self.write(&id.name);
25841                    self.write("\"");
25842                } else {
25843                    self.write(&id.name);
25844                }
25845                Ok(())
25846            }
25847            Expression::Literal(Literal::String(s)) => {
25848                // Output string with quotes
25849                self.write("'");
25850                self.write(s);
25851                self.write("'");
25852                Ok(())
25853            }
25854            _ => self.generate_expression(expr),
25855        }
25856    }
25857
25858    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25859        self.write_keyword(&e.name);
25860        if let Some(ref value) = e.value {
25861            if e.eq {
25862                self.write(" = ");
25863            } else {
25864                self.write(" ");
25865            }
25866            self.generate_expression(value)?;
25867        }
25868        if !e.values.is_empty() {
25869            if e.eq {
25870                self.write(" = ");
25871            } else {
25872                self.write(" ");
25873            }
25874            self.write("(");
25875            for (i, v) in e.values.iter().enumerate() {
25876                if i > 0 {
25877                    self.write(", ");
25878                }
25879                self.generate_expression(v)?;
25880            }
25881            self.write(")");
25882        }
25883        Ok(())
25884    }
25885
25886    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25887        // CORR(this, expression)
25888        self.write_keyword("CORR");
25889        self.write("(");
25890        self.generate_expression(&e.this)?;
25891        self.write(", ");
25892        self.generate_expression(&e.expression)?;
25893        self.write(")");
25894        Ok(())
25895    }
25896
25897    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25898        // COSINE_DISTANCE(this, expression)
25899        self.write_keyword("COSINE_DISTANCE");
25900        self.write("(");
25901        self.generate_expression(&e.this)?;
25902        self.write(", ");
25903        self.generate_expression(&e.expression)?;
25904        self.write(")");
25905        Ok(())
25906    }
25907
25908    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25909        // COVAR_POP(this, expression)
25910        self.write_keyword("COVAR_POP");
25911        self.write("(");
25912        self.generate_expression(&e.this)?;
25913        self.write(", ");
25914        self.generate_expression(&e.expression)?;
25915        self.write(")");
25916        Ok(())
25917    }
25918
25919    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25920        // COVAR_SAMP(this, expression)
25921        self.write_keyword("COVAR_SAMP");
25922        self.write("(");
25923        self.generate_expression(&e.this)?;
25924        self.write(", ");
25925        self.generate_expression(&e.expression)?;
25926        self.write(")");
25927        Ok(())
25928    }
25929
25930    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25931        // CREDENTIALS (key1='value1', key2='value2')
25932        self.write_keyword("CREDENTIALS");
25933        self.write(" (");
25934        for (i, (key, value)) in e.credentials.iter().enumerate() {
25935            if i > 0 {
25936                self.write(", ");
25937            }
25938            self.write(key);
25939            self.write("='");
25940            self.write(value);
25941            self.write("'");
25942        }
25943        self.write(")");
25944        Ok(())
25945    }
25946
25947    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25948        // CREDENTIALS=(expressions)
25949        self.write_keyword("CREDENTIALS");
25950        self.write("=(");
25951        for (i, expr) in e.expressions.iter().enumerate() {
25952            if i > 0 {
25953                self.write(", ");
25954            }
25955            self.generate_expression(expr)?;
25956        }
25957        self.write(")");
25958        Ok(())
25959    }
25960
25961    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25962        use crate::dialects::DialectType;
25963
25964        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25965        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25966        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25967            self.generate_expression(&e.this)?;
25968            self.write_space();
25969            self.write_keyword("AS");
25970            self.write_space();
25971            self.generate_identifier(&e.alias)?;
25972            return Ok(());
25973        }
25974        self.write(&e.alias.name);
25975
25976        // BigQuery doesn't support column aliases in CTE definitions
25977        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25978
25979        if !e.columns.is_empty() && !skip_cte_columns {
25980            self.write("(");
25981            for (i, col) in e.columns.iter().enumerate() {
25982                if i > 0 {
25983                    self.write(", ");
25984                }
25985                self.write(&col.name);
25986            }
25987            self.write(")");
25988        }
25989        // USING KEY (columns) for DuckDB recursive CTEs
25990        if !e.key_expressions.is_empty() {
25991            self.write_space();
25992            self.write_keyword("USING KEY");
25993            self.write(" (");
25994            for (i, key) in e.key_expressions.iter().enumerate() {
25995                if i > 0 {
25996                    self.write(", ");
25997                }
25998                self.write(&key.name);
25999            }
26000            self.write(")");
26001        }
26002        self.write_space();
26003        self.write_keyword("AS");
26004        self.write_space();
26005        if let Some(materialized) = e.materialized {
26006            if materialized {
26007                self.write_keyword("MATERIALIZED");
26008            } else {
26009                self.write_keyword("NOT MATERIALIZED");
26010            }
26011            self.write_space();
26012        }
26013        self.write("(");
26014        self.generate_expression(&e.this)?;
26015        self.write(")");
26016        Ok(())
26017    }
26018
26019    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
26020        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
26021        if e.expressions.is_empty() {
26022            self.write_keyword("WITH CUBE");
26023        } else {
26024            self.write_keyword("CUBE");
26025            self.write("(");
26026            for (i, expr) in e.expressions.iter().enumerate() {
26027                if i > 0 {
26028                    self.write(", ");
26029                }
26030                self.generate_expression(expr)?;
26031            }
26032            self.write(")");
26033        }
26034        Ok(())
26035    }
26036
26037    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
26038        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
26039        self.write_keyword("CURRENT_DATETIME");
26040        if let Some(this) = &e.this {
26041            self.write("(");
26042            self.generate_expression(this)?;
26043            self.write(")");
26044        }
26045        Ok(())
26046    }
26047
26048    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
26049        // CURRENT_SCHEMA - no arguments
26050        self.write_keyword("CURRENT_SCHEMA");
26051        Ok(())
26052    }
26053
26054    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
26055        // CURRENT_SCHEMAS(include_implicit)
26056        self.write_keyword("CURRENT_SCHEMAS");
26057        self.write("(");
26058        if let Some(this) = &e.this {
26059            self.generate_expression(this)?;
26060        }
26061        self.write(")");
26062        Ok(())
26063    }
26064
26065    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
26066        // CURRENT_USER or CURRENT_USER()
26067        self.write_keyword("CURRENT_USER");
26068        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
26069        let needs_parens = e.this.is_some()
26070            || matches!(
26071                self.config.dialect,
26072                Some(DialectType::Snowflake)
26073                    | Some(DialectType::Spark)
26074                    | Some(DialectType::Hive)
26075                    | Some(DialectType::DuckDB)
26076                    | Some(DialectType::BigQuery)
26077                    | Some(DialectType::MySQL)
26078                    | Some(DialectType::Databricks)
26079            );
26080        if needs_parens {
26081            self.write("()");
26082        }
26083        Ok(())
26084    }
26085
26086    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26087        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26088        if self.config.dialect == Some(DialectType::Solr) {
26089            self.generate_expression(&e.this)?;
26090            self.write(" ");
26091            self.write_keyword("OR");
26092            self.write(" ");
26093            self.generate_expression(&e.expression)?;
26094        } else if self.config.dialect == Some(DialectType::MySQL) {
26095            self.generate_mysql_concat_from_dpipe(e)?;
26096        } else {
26097            // String concatenation: this || expression
26098            self.generate_expression(&e.this)?;
26099            self.write(" || ");
26100            self.generate_expression(&e.expression)?;
26101        }
26102        Ok(())
26103    }
26104
26105    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26106        // DATABLOCKSIZE=... (Teradata)
26107        self.write_keyword("DATABLOCKSIZE");
26108        self.write("=");
26109        if let Some(size) = e.size {
26110            self.write(&size.to_string());
26111            if let Some(units) = &e.units {
26112                self.write_space();
26113                self.generate_expression(units)?;
26114            }
26115        } else if e.minimum.is_some() {
26116            self.write_keyword("MINIMUM");
26117        } else if e.maximum.is_some() {
26118            self.write_keyword("MAXIMUM");
26119        } else if e.default.is_some() {
26120            self.write_keyword("DEFAULT");
26121        }
26122        Ok(())
26123    }
26124
26125    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26126        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26127        self.write_keyword("DATA_DELETION");
26128        self.write("=");
26129
26130        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26131        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26132
26133        if is_on {
26134            self.write_keyword("ON");
26135            if has_options {
26136                self.write("(");
26137                let mut first = true;
26138                if let Some(filter_column) = &e.filter_column {
26139                    self.write_keyword("FILTER_COLUMN");
26140                    self.write("=");
26141                    self.generate_expression(filter_column)?;
26142                    first = false;
26143                }
26144                if let Some(retention_period) = &e.retention_period {
26145                    if !first {
26146                        self.write(", ");
26147                    }
26148                    self.write_keyword("RETENTION_PERIOD");
26149                    self.write("=");
26150                    self.generate_expression(retention_period)?;
26151                }
26152                self.write(")");
26153            }
26154        } else {
26155            self.write_keyword("OFF");
26156        }
26157        Ok(())
26158    }
26159
26160    /// Generate a Date function expression
26161    /// For Exasol: {d'value'} -> TO_DATE('value')
26162    /// For other dialects: DATE('value')
26163    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26164        use crate::dialects::DialectType;
26165        use crate::expressions::Literal;
26166
26167        match self.config.dialect {
26168            // Exasol uses TO_DATE for Date expressions
26169            Some(DialectType::Exasol) => {
26170                self.write_keyword("TO_DATE");
26171                self.write("(");
26172                // Extract the string value from the expression if it's a string literal
26173                match &e.this {
26174                    Expression::Literal(Literal::String(s)) => {
26175                        self.write("'");
26176                        self.write(s);
26177                        self.write("'");
26178                    }
26179                    _ => {
26180                        self.generate_expression(&e.this)?;
26181                    }
26182                }
26183                self.write(")");
26184            }
26185            // Standard: DATE(value)
26186            _ => {
26187                self.write_keyword("DATE");
26188                self.write("(");
26189                self.generate_expression(&e.this)?;
26190                self.write(")");
26191            }
26192        }
26193        Ok(())
26194    }
26195
26196    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26197        // DATE_BIN(interval, timestamp[, origin])
26198        self.write_keyword("DATE_BIN");
26199        self.write("(");
26200        self.generate_expression(&e.this)?;
26201        self.write(", ");
26202        self.generate_expression(&e.expression)?;
26203        if let Some(origin) = &e.origin {
26204            self.write(", ");
26205            self.generate_expression(origin)?;
26206        }
26207        self.write(")");
26208        Ok(())
26209    }
26210
26211    fn generate_date_format_column_constraint(
26212        &mut self,
26213        e: &DateFormatColumnConstraint,
26214    ) -> Result<()> {
26215        // FORMAT 'format_string' (Teradata)
26216        self.write_keyword("FORMAT");
26217        self.write_space();
26218        self.generate_expression(&e.this)?;
26219        Ok(())
26220    }
26221
26222    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26223        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26224        self.write_keyword("DATE_FROM_PARTS");
26225        self.write("(");
26226        let mut first = true;
26227        if let Some(year) = &e.year {
26228            self.generate_expression(year)?;
26229            first = false;
26230        }
26231        if let Some(month) = &e.month {
26232            if !first {
26233                self.write(", ");
26234            }
26235            self.generate_expression(month)?;
26236            first = false;
26237        }
26238        if let Some(day) = &e.day {
26239            if !first {
26240                self.write(", ");
26241            }
26242            self.generate_expression(day)?;
26243        }
26244        self.write(")");
26245        Ok(())
26246    }
26247
26248    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26249        // DATETIME(this) or DATETIME(this, expression)
26250        self.write_keyword("DATETIME");
26251        self.write("(");
26252        self.generate_expression(&e.this)?;
26253        if let Some(expr) = &e.expression {
26254            self.write(", ");
26255            self.generate_expression(expr)?;
26256        }
26257        self.write(")");
26258        Ok(())
26259    }
26260
26261    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26262        // DATETIME_ADD(this, expression, unit)
26263        self.write_keyword("DATETIME_ADD");
26264        self.write("(");
26265        self.generate_expression(&e.this)?;
26266        self.write(", ");
26267        self.generate_expression(&e.expression)?;
26268        if let Some(unit) = &e.unit {
26269            self.write(", ");
26270            self.write_keyword(unit);
26271        }
26272        self.write(")");
26273        Ok(())
26274    }
26275
26276    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26277        // DATETIME_DIFF(this, expression, unit)
26278        self.write_keyword("DATETIME_DIFF");
26279        self.write("(");
26280        self.generate_expression(&e.this)?;
26281        self.write(", ");
26282        self.generate_expression(&e.expression)?;
26283        if let Some(unit) = &e.unit {
26284            self.write(", ");
26285            self.write_keyword(unit);
26286        }
26287        self.write(")");
26288        Ok(())
26289    }
26290
26291    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26292        // DATETIME_SUB(this, expression, unit)
26293        self.write_keyword("DATETIME_SUB");
26294        self.write("(");
26295        self.generate_expression(&e.this)?;
26296        self.write(", ");
26297        self.generate_expression(&e.expression)?;
26298        if let Some(unit) = &e.unit {
26299            self.write(", ");
26300            self.write_keyword(unit);
26301        }
26302        self.write(")");
26303        Ok(())
26304    }
26305
26306    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26307        // DATETIME_TRUNC(this, unit, zone)
26308        self.write_keyword("DATETIME_TRUNC");
26309        self.write("(");
26310        self.generate_expression(&e.this)?;
26311        self.write(", ");
26312        self.write_keyword(&e.unit);
26313        if let Some(zone) = &e.zone {
26314            self.write(", ");
26315            self.generate_expression(zone)?;
26316        }
26317        self.write(")");
26318        Ok(())
26319    }
26320
26321    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26322        // DAYNAME(this)
26323        self.write_keyword("DAYNAME");
26324        self.write("(");
26325        self.generate_expression(&e.this)?;
26326        self.write(")");
26327        Ok(())
26328    }
26329
26330    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26331        // DECLARE var1 AS type1, var2 AS type2, ...
26332        self.write_keyword("DECLARE");
26333        self.write_space();
26334        for (i, expr) in e.expressions.iter().enumerate() {
26335            if i > 0 {
26336                self.write(", ");
26337            }
26338            self.generate_expression(expr)?;
26339        }
26340        Ok(())
26341    }
26342
26343    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26344        use crate::dialects::DialectType;
26345
26346        // variable TYPE [DEFAULT default]
26347        self.generate_expression(&e.this)?;
26348        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26349        for name in &e.additional_names {
26350            self.write(", ");
26351            self.generate_expression(name)?;
26352        }
26353        if let Some(kind) = &e.kind {
26354            self.write_space();
26355            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26356            // TSQL: Always includes AS (normalization)
26357            // Others: Include AS if present in original
26358            match self.config.dialect {
26359                Some(DialectType::BigQuery) => {
26360                    self.write(kind);
26361                }
26362                Some(DialectType::TSQL) => {
26363                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26364                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26365                    // NONCLUSTERED, or INDEX constraints
26366                    let is_complex_table = kind.starts_with("TABLE")
26367                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26368
26369                    if is_complex_table {
26370                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26371                        self.write(kind);
26372                    } else {
26373                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26374                        if !kind.starts_with("CURSOR") {
26375                            self.write_keyword("AS");
26376                            self.write_space();
26377                        }
26378                        // Normalize INT to INTEGER for TSQL DECLARE statements
26379                        if kind == "INT" {
26380                            self.write("INTEGER");
26381                        } else if kind.starts_with("TABLE") {
26382                            // Normalize INT to INTEGER inside TABLE column definitions
26383                            let normalized = kind
26384                                .replace(" INT ", " INTEGER ")
26385                                .replace(" INT,", " INTEGER,")
26386                                .replace(" INT)", " INTEGER)")
26387                                .replace("(INT ", "(INTEGER ");
26388                            self.write(&normalized);
26389                        } else {
26390                            self.write(kind);
26391                        }
26392                    }
26393                }
26394                _ => {
26395                    if e.has_as {
26396                        self.write_keyword("AS");
26397                        self.write_space();
26398                    }
26399                    self.write(kind);
26400                }
26401            }
26402        }
26403        if let Some(default) = &e.default {
26404            // BigQuery uses DEFAULT, others use =
26405            match self.config.dialect {
26406                Some(DialectType::BigQuery) => {
26407                    self.write_space();
26408                    self.write_keyword("DEFAULT");
26409                    self.write_space();
26410                }
26411                _ => {
26412                    self.write(" = ");
26413                }
26414            }
26415            self.generate_expression(default)?;
26416        }
26417        Ok(())
26418    }
26419
26420    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26421        // DECODE(expr, search1, result1, search2, result2, ..., default)
26422        self.write_keyword("DECODE");
26423        self.write("(");
26424        for (i, expr) in e.expressions.iter().enumerate() {
26425            if i > 0 {
26426                self.write(", ");
26427            }
26428            self.generate_expression(expr)?;
26429        }
26430        self.write(")");
26431        Ok(())
26432    }
26433
26434    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26435        // DECOMPRESS(expr, 'method')
26436        self.write_keyword("DECOMPRESS");
26437        self.write("(");
26438        self.generate_expression(&e.this)?;
26439        self.write(", '");
26440        self.write(&e.method);
26441        self.write("')");
26442        Ok(())
26443    }
26444
26445    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26446        // DECOMPRESS(expr, 'method')
26447        self.write_keyword("DECOMPRESS");
26448        self.write("(");
26449        self.generate_expression(&e.this)?;
26450        self.write(", '");
26451        self.write(&e.method);
26452        self.write("')");
26453        Ok(())
26454    }
26455
26456    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26457        // DECRYPT(value, passphrase [, aad [, algorithm]])
26458        self.write_keyword("DECRYPT");
26459        self.write("(");
26460        self.generate_expression(&e.this)?;
26461        if let Some(passphrase) = &e.passphrase {
26462            self.write(", ");
26463            self.generate_expression(passphrase)?;
26464        }
26465        if let Some(aad) = &e.aad {
26466            self.write(", ");
26467            self.generate_expression(aad)?;
26468        }
26469        if let Some(method) = &e.encryption_method {
26470            self.write(", ");
26471            self.generate_expression(method)?;
26472        }
26473        self.write(")");
26474        Ok(())
26475    }
26476
26477    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26478        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26479        self.write_keyword("DECRYPT_RAW");
26480        self.write("(");
26481        self.generate_expression(&e.this)?;
26482        if let Some(key) = &e.key {
26483            self.write(", ");
26484            self.generate_expression(key)?;
26485        }
26486        if let Some(iv) = &e.iv {
26487            self.write(", ");
26488            self.generate_expression(iv)?;
26489        }
26490        if let Some(aad) = &e.aad {
26491            self.write(", ");
26492            self.generate_expression(aad)?;
26493        }
26494        if let Some(method) = &e.encryption_method {
26495            self.write(", ");
26496            self.generate_expression(method)?;
26497        }
26498        self.write(")");
26499        Ok(())
26500    }
26501
26502    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26503        // DEFINER = user
26504        self.write_keyword("DEFINER");
26505        self.write(" = ");
26506        self.generate_expression(&e.this)?;
26507        Ok(())
26508    }
26509
26510    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26511        // Python: DETACH[DATABASE IF EXISTS] this
26512        self.write_keyword("DETACH");
26513        if e.exists {
26514            self.write_keyword(" DATABASE IF EXISTS");
26515        }
26516        self.write_space();
26517        self.generate_expression(&e.this)?;
26518        Ok(())
26519    }
26520
26521    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26522        let property_name = match e.this.as_ref() {
26523            Expression::Identifier(id) => id.name.as_str(),
26524            Expression::Var(v) => v.this.as_str(),
26525            _ => "DICTIONARY",
26526        };
26527        self.write_keyword(property_name);
26528        self.write("(");
26529        self.write(&e.kind);
26530        if let Some(settings) = &e.settings {
26531            self.write("(");
26532            if let Expression::Tuple(t) = settings.as_ref() {
26533                if self.config.pretty && !t.expressions.is_empty() {
26534                    self.write_newline();
26535                    self.indent_level += 1;
26536                    for (i, pair) in t.expressions.iter().enumerate() {
26537                        if i > 0 {
26538                            self.write(",");
26539                            self.write_newline();
26540                        }
26541                        self.write_indent();
26542                        if let Expression::Tuple(pair_tuple) = pair {
26543                            if let Some(k) = pair_tuple.expressions.first() {
26544                                self.generate_expression(k)?;
26545                            }
26546                            if let Some(v) = pair_tuple.expressions.get(1) {
26547                                self.write(" ");
26548                                self.generate_expression(v)?;
26549                            }
26550                        } else {
26551                            self.generate_expression(pair)?;
26552                        }
26553                    }
26554                    self.indent_level -= 1;
26555                    self.write_newline();
26556                    self.write_indent();
26557                } else {
26558                    for (i, pair) in t.expressions.iter().enumerate() {
26559                        if i > 0 {
26560                            self.write(", ");
26561                        }
26562                        if let Expression::Tuple(pair_tuple) = pair {
26563                            if let Some(k) = pair_tuple.expressions.first() {
26564                                self.generate_expression(k)?;
26565                            }
26566                            if let Some(v) = pair_tuple.expressions.get(1) {
26567                                self.write(" ");
26568                                self.generate_expression(v)?;
26569                            }
26570                        } else {
26571                            self.generate_expression(pair)?;
26572                        }
26573                    }
26574                }
26575            } else {
26576                self.generate_expression(settings)?;
26577            }
26578            self.write(")");
26579        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26580            self.write("()");
26581        }
26582        self.write(")");
26583        Ok(())
26584    }
26585
26586    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26587        let property_name = match e.this.as_ref() {
26588            Expression::Identifier(id) => id.name.as_str(),
26589            Expression::Var(v) => v.this.as_str(),
26590            _ => "RANGE",
26591        };
26592        self.write_keyword(property_name);
26593        self.write("(");
26594        if let Some(min) = &e.min {
26595            self.write_keyword("MIN");
26596            self.write_space();
26597            self.generate_expression(min)?;
26598        }
26599        if let Some(max) = &e.max {
26600            self.write_space();
26601            self.write_keyword("MAX");
26602            self.write_space();
26603            self.generate_expression(max)?;
26604        }
26605        self.write(")");
26606        Ok(())
26607    }
26608
26609    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26610        // Python: {local}DIRECTORY {this}{row_format}
26611        if e.local.is_some() {
26612            self.write_keyword("LOCAL ");
26613        }
26614        self.write_keyword("DIRECTORY");
26615        self.write_space();
26616        self.generate_expression(&e.this)?;
26617        if let Some(row_format) = &e.row_format {
26618            self.write_space();
26619            self.generate_expression(row_format)?;
26620        }
26621        Ok(())
26622    }
26623
26624    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26625        // Redshift: DISTKEY(column)
26626        self.write_keyword("DISTKEY");
26627        self.write("(");
26628        self.generate_expression(&e.this)?;
26629        self.write(")");
26630        Ok(())
26631    }
26632
26633    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26634        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26635        self.write_keyword("DISTSTYLE");
26636        self.write_space();
26637        self.generate_expression(&e.this)?;
26638        Ok(())
26639    }
26640
26641    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26642        // Python: "DISTRIBUTE BY" expressions
26643        self.write_keyword("DISTRIBUTE BY");
26644        self.write_space();
26645        for (i, expr) in e.expressions.iter().enumerate() {
26646            if i > 0 {
26647                self.write(", ");
26648            }
26649            self.generate_expression(expr)?;
26650        }
26651        Ok(())
26652    }
26653
26654    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26655        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26656        self.write_keyword("DISTRIBUTED BY");
26657        self.write_space();
26658        self.write(&e.kind);
26659        if !e.expressions.is_empty() {
26660            self.write(" (");
26661            for (i, expr) in e.expressions.iter().enumerate() {
26662                if i > 0 {
26663                    self.write(", ");
26664                }
26665                self.generate_expression(expr)?;
26666            }
26667            self.write(")");
26668        }
26669        if let Some(buckets) = &e.buckets {
26670            self.write_space();
26671            self.write_keyword("BUCKETS");
26672            self.write_space();
26673            self.generate_expression(buckets)?;
26674        }
26675        if let Some(order) = &e.order {
26676            self.write_space();
26677            self.generate_expression(order)?;
26678        }
26679        Ok(())
26680    }
26681
26682    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26683        // DOT_PRODUCT(vector1, vector2)
26684        self.write_keyword("DOT_PRODUCT");
26685        self.write("(");
26686        self.generate_expression(&e.this)?;
26687        self.write(", ");
26688        self.generate_expression(&e.expression)?;
26689        self.write(")");
26690        Ok(())
26691    }
26692
26693    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26694        // Python: DROP{IF EXISTS }expressions
26695        self.write_keyword("DROP");
26696        if e.exists {
26697            self.write_keyword(" IF EXISTS ");
26698        } else {
26699            self.write_space();
26700        }
26701        for (i, expr) in e.expressions.iter().enumerate() {
26702            if i > 0 {
26703                self.write(", ");
26704            }
26705            self.generate_expression(expr)?;
26706        }
26707        Ok(())
26708    }
26709
26710    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26711        // Python: DUPLICATE KEY (expressions)
26712        self.write_keyword("DUPLICATE KEY");
26713        self.write(" (");
26714        for (i, expr) in e.expressions.iter().enumerate() {
26715            if i > 0 {
26716                self.write(", ");
26717            }
26718            self.generate_expression(expr)?;
26719        }
26720        self.write(")");
26721        Ok(())
26722    }
26723
26724    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26725        // ELT(index, str1, str2, ...)
26726        self.write_keyword("ELT");
26727        self.write("(");
26728        self.generate_expression(&e.this)?;
26729        for expr in &e.expressions {
26730            self.write(", ");
26731            self.generate_expression(expr)?;
26732        }
26733        self.write(")");
26734        Ok(())
26735    }
26736
26737    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26738        // ENCODE(string, charset)
26739        self.write_keyword("ENCODE");
26740        self.write("(");
26741        self.generate_expression(&e.this)?;
26742        if let Some(charset) = &e.charset {
26743            self.write(", ");
26744            self.generate_expression(charset)?;
26745        }
26746        self.write(")");
26747        Ok(())
26748    }
26749
26750    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26751        // Python: [KEY ]ENCODE this [properties]
26752        if e.key.is_some() {
26753            self.write_keyword("KEY ");
26754        }
26755        self.write_keyword("ENCODE");
26756        self.write_space();
26757        self.generate_expression(&e.this)?;
26758        if !e.properties.is_empty() {
26759            self.write(" (");
26760            for (i, prop) in e.properties.iter().enumerate() {
26761                if i > 0 {
26762                    self.write(", ");
26763                }
26764                self.generate_expression(prop)?;
26765            }
26766            self.write(")");
26767        }
26768        Ok(())
26769    }
26770
26771    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26772        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26773        self.write_keyword("ENCRYPT");
26774        self.write("(");
26775        self.generate_expression(&e.this)?;
26776        if let Some(passphrase) = &e.passphrase {
26777            self.write(", ");
26778            self.generate_expression(passphrase)?;
26779        }
26780        if let Some(aad) = &e.aad {
26781            self.write(", ");
26782            self.generate_expression(aad)?;
26783        }
26784        if let Some(method) = &e.encryption_method {
26785            self.write(", ");
26786            self.generate_expression(method)?;
26787        }
26788        self.write(")");
26789        Ok(())
26790    }
26791
26792    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26793        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26794        self.write_keyword("ENCRYPT_RAW");
26795        self.write("(");
26796        self.generate_expression(&e.this)?;
26797        if let Some(key) = &e.key {
26798            self.write(", ");
26799            self.generate_expression(key)?;
26800        }
26801        if let Some(iv) = &e.iv {
26802            self.write(", ");
26803            self.generate_expression(iv)?;
26804        }
26805        if let Some(aad) = &e.aad {
26806            self.write(", ");
26807            self.generate_expression(aad)?;
26808        }
26809        if let Some(method) = &e.encryption_method {
26810            self.write(", ");
26811            self.generate_expression(method)?;
26812        }
26813        self.write(")");
26814        Ok(())
26815    }
26816
26817    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26818        // MySQL: ENGINE = InnoDB
26819        self.write_keyword("ENGINE");
26820        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26821            self.write("=");
26822        } else {
26823            self.write(" = ");
26824        }
26825        self.generate_expression(&e.this)?;
26826        Ok(())
26827    }
26828
26829    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26830        // ENVIRONMENT (expressions)
26831        self.write_keyword("ENVIRONMENT");
26832        self.write(" (");
26833        for (i, expr) in e.expressions.iter().enumerate() {
26834            if i > 0 {
26835                self.write(", ");
26836            }
26837            self.generate_expression(expr)?;
26838        }
26839        self.write(")");
26840        Ok(())
26841    }
26842
26843    fn generate_ephemeral_column_constraint(
26844        &mut self,
26845        e: &EphemeralColumnConstraint,
26846    ) -> Result<()> {
26847        // MySQL: EPHEMERAL [expr]
26848        self.write_keyword("EPHEMERAL");
26849        if let Some(this) = &e.this {
26850            self.write_space();
26851            self.generate_expression(this)?;
26852        }
26853        Ok(())
26854    }
26855
26856    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26857        // Snowflake: EQUAL_NULL(a, b)
26858        self.write_keyword("EQUAL_NULL");
26859        self.write("(");
26860        self.generate_expression(&e.this)?;
26861        self.write(", ");
26862        self.generate_expression(&e.expression)?;
26863        self.write(")");
26864        Ok(())
26865    }
26866
26867    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26868        use crate::dialects::DialectType;
26869
26870        // PostgreSQL uses <-> operator syntax
26871        match self.config.dialect {
26872            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26873                self.generate_expression(&e.this)?;
26874                self.write(" <-> ");
26875                self.generate_expression(&e.expression)?;
26876            }
26877            _ => {
26878                // Other dialects use EUCLIDEAN_DISTANCE function
26879                self.write_keyword("EUCLIDEAN_DISTANCE");
26880                self.write("(");
26881                self.generate_expression(&e.this)?;
26882                self.write(", ");
26883                self.generate_expression(&e.expression)?;
26884                self.write(")");
26885            }
26886        }
26887        Ok(())
26888    }
26889
26890    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26891        // EXECUTE AS CALLER|OWNER|user
26892        self.write_keyword("EXECUTE AS");
26893        self.write_space();
26894        self.generate_expression(&e.this)?;
26895        Ok(())
26896    }
26897
26898    fn generate_export(&mut self, e: &Export) -> Result<()> {
26899        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26900        self.write_keyword("EXPORT DATA");
26901        if let Some(connection) = &e.connection {
26902            self.write_space();
26903            self.write_keyword("WITH CONNECTION");
26904            self.write_space();
26905            self.generate_expression(connection)?;
26906        }
26907        if !e.options.is_empty() {
26908            self.write_space();
26909            self.generate_options_clause(&e.options)?;
26910        }
26911        self.write_space();
26912        self.write_keyword("AS");
26913        self.write_space();
26914        self.generate_expression(&e.this)?;
26915        Ok(())
26916    }
26917
26918    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26919        // EXTERNAL [this]
26920        self.write_keyword("EXTERNAL");
26921        if let Some(this) = &e.this {
26922            self.write_space();
26923            self.generate_expression(this)?;
26924        }
26925        Ok(())
26926    }
26927
26928    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26929        // Python: {no}FALLBACK{protection}
26930        if e.no.is_some() {
26931            self.write_keyword("NO ");
26932        }
26933        self.write_keyword("FALLBACK");
26934        if e.protection.is_some() {
26935            self.write_keyword(" PROTECTION");
26936        }
26937        Ok(())
26938    }
26939
26940    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26941        // BigQuery: FARM_FINGERPRINT(value)
26942        self.write_keyword("FARM_FINGERPRINT");
26943        self.write("(");
26944        for (i, expr) in e.expressions.iter().enumerate() {
26945            if i > 0 {
26946                self.write(", ");
26947            }
26948            self.generate_expression(expr)?;
26949        }
26950        self.write(")");
26951        Ok(())
26952    }
26953
26954    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26955        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26956        self.write_keyword("FEATURES_AT_TIME");
26957        self.write("(");
26958        self.generate_expression(&e.this)?;
26959        if let Some(time) = &e.time {
26960            self.write(", ");
26961            self.generate_expression(time)?;
26962        }
26963        if let Some(num_rows) = &e.num_rows {
26964            self.write(", ");
26965            self.generate_expression(num_rows)?;
26966        }
26967        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26968            self.write(", ");
26969            self.generate_expression(ignore_nulls)?;
26970        }
26971        self.write(")");
26972        Ok(())
26973    }
26974
26975    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26976        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26977        let use_limit = !e.percent
26978            && !e.with_ties
26979            && e.count.is_some()
26980            && matches!(
26981                self.config.dialect,
26982                Some(DialectType::Spark)
26983                    | Some(DialectType::Hive)
26984                    | Some(DialectType::DuckDB)
26985                    | Some(DialectType::SQLite)
26986                    | Some(DialectType::MySQL)
26987                    | Some(DialectType::BigQuery)
26988                    | Some(DialectType::Databricks)
26989                    | Some(DialectType::StarRocks)
26990                    | Some(DialectType::Doris)
26991                    | Some(DialectType::Athena)
26992                    | Some(DialectType::ClickHouse)
26993            );
26994
26995        if use_limit {
26996            self.write_keyword("LIMIT");
26997            self.write_space();
26998            self.generate_expression(e.count.as_ref().unwrap())?;
26999            return Ok(());
27000        }
27001
27002        // Python: FETCH direction count limit_options
27003        self.write_keyword("FETCH");
27004        if !e.direction.is_empty() {
27005            self.write_space();
27006            self.write_keyword(&e.direction);
27007        }
27008        if let Some(count) = &e.count {
27009            self.write_space();
27010            self.generate_expression(count)?;
27011        }
27012        // Generate PERCENT, ROWS, WITH TIES/ONLY
27013        if e.percent {
27014            self.write_keyword(" PERCENT");
27015        }
27016        if e.rows {
27017            self.write_keyword(" ROWS");
27018        }
27019        if e.with_ties {
27020            self.write_keyword(" WITH TIES");
27021        } else if e.rows {
27022            self.write_keyword(" ONLY");
27023        } else {
27024            self.write_keyword(" ROWS ONLY");
27025        }
27026        Ok(())
27027    }
27028
27029    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
27030        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
27031        // For Spark/Databricks without hive_format: USING this
27032        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
27033        if e.hive_format.is_some() {
27034            // Hive format: STORED AS ...
27035            self.write_keyword("STORED AS");
27036            self.write_space();
27037            if let Some(this) = &e.this {
27038                // Uppercase the format name (e.g., parquet -> PARQUET)
27039                if let Expression::Identifier(id) = this.as_ref() {
27040                    self.write_keyword(&id.name.to_uppercase());
27041                } else {
27042                    self.generate_expression(this)?;
27043                }
27044            }
27045        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
27046            // Hive: STORED AS format
27047            self.write_keyword("STORED AS");
27048            self.write_space();
27049            if let Some(this) = &e.this {
27050                if let Expression::Identifier(id) = this.as_ref() {
27051                    self.write_keyword(&id.name.to_uppercase());
27052                } else {
27053                    self.generate_expression(this)?;
27054                }
27055            }
27056        } else if matches!(
27057            self.config.dialect,
27058            Some(DialectType::Spark) | Some(DialectType::Databricks)
27059        ) {
27060            // Spark/Databricks: USING format (e.g., USING DELTA)
27061            self.write_keyword("USING");
27062            self.write_space();
27063            if let Some(this) = &e.this {
27064                self.generate_expression(this)?;
27065            }
27066        } else {
27067            // Snowflake/standard format
27068            self.write_keyword("FILE_FORMAT");
27069            self.write(" = ");
27070            if let Some(this) = &e.this {
27071                self.generate_expression(this)?;
27072            } else if !e.expressions.is_empty() {
27073                self.write("(");
27074                for (i, expr) in e.expressions.iter().enumerate() {
27075                    if i > 0 {
27076                        self.write(", ");
27077                    }
27078                    self.generate_expression(expr)?;
27079                }
27080                self.write(")");
27081            }
27082        }
27083        Ok(())
27084    }
27085
27086    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27087        // agg_func FILTER(WHERE condition)
27088        self.generate_expression(&e.this)?;
27089        self.write_space();
27090        self.write_keyword("FILTER");
27091        self.write("(");
27092        self.write_keyword("WHERE");
27093        self.write_space();
27094        self.generate_expression(&e.expression)?;
27095        self.write(")");
27096        Ok(())
27097    }
27098
27099    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27100        // FLOAT64(this) or FLOAT64(this, expression)
27101        self.write_keyword("FLOAT64");
27102        self.write("(");
27103        self.generate_expression(&e.this)?;
27104        if let Some(expr) = &e.expression {
27105            self.write(", ");
27106            self.generate_expression(expr)?;
27107        }
27108        self.write(")");
27109        Ok(())
27110    }
27111
27112    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27113        // FOR this DO expression
27114        self.write_keyword("FOR");
27115        self.write_space();
27116        self.generate_expression(&e.this)?;
27117        self.write_space();
27118        self.write_keyword("DO");
27119        self.write_space();
27120        self.generate_expression(&e.expression)?;
27121        Ok(())
27122    }
27123
27124    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27125        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27126        self.write_keyword("FOREIGN KEY");
27127        if !e.expressions.is_empty() {
27128            self.write(" (");
27129            for (i, expr) in e.expressions.iter().enumerate() {
27130                if i > 0 {
27131                    self.write(", ");
27132                }
27133                self.generate_expression(expr)?;
27134            }
27135            self.write(")");
27136        }
27137        if let Some(reference) = &e.reference {
27138            self.write_space();
27139            self.generate_expression(reference)?;
27140        }
27141        if let Some(delete) = &e.delete {
27142            self.write_space();
27143            self.write_keyword("ON DELETE");
27144            self.write_space();
27145            self.generate_expression(delete)?;
27146        }
27147        if let Some(update) = &e.update {
27148            self.write_space();
27149            self.write_keyword("ON UPDATE");
27150            self.write_space();
27151            self.generate_expression(update)?;
27152        }
27153        if !e.options.is_empty() {
27154            self.write_space();
27155            for (i, opt) in e.options.iter().enumerate() {
27156                if i > 0 {
27157                    self.write_space();
27158                }
27159                self.generate_expression(opt)?;
27160            }
27161        }
27162        Ok(())
27163    }
27164
27165    fn generate_format(&mut self, e: &Format) -> Result<()> {
27166        // FORMAT(this, expressions...)
27167        self.write_keyword("FORMAT");
27168        self.write("(");
27169        self.generate_expression(&e.this)?;
27170        for expr in &e.expressions {
27171            self.write(", ");
27172            self.generate_expression(expr)?;
27173        }
27174        self.write(")");
27175        Ok(())
27176    }
27177
27178    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27179        // Teradata: column (FORMAT 'format_string')
27180        self.generate_expression(&e.this)?;
27181        self.write(" (");
27182        self.write_keyword("FORMAT");
27183        self.write(" '");
27184        self.write(&e.format);
27185        self.write("')");
27186        Ok(())
27187    }
27188
27189    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27190        // Python: FREESPACE=this[PERCENT]
27191        self.write_keyword("FREESPACE");
27192        self.write("=");
27193        self.generate_expression(&e.this)?;
27194        if e.percent.is_some() {
27195            self.write_keyword(" PERCENT");
27196        }
27197        Ok(())
27198    }
27199
27200    fn generate_from(&mut self, e: &From) -> Result<()> {
27201        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27202        self.write_keyword("FROM");
27203        self.write_space();
27204
27205        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27206        // But keep commas when TABLESAMPLE is present
27207        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27208        use crate::dialects::DialectType;
27209        let has_tablesample = e
27210            .expressions
27211            .iter()
27212            .any(|expr| matches!(expr, Expression::TableSample(_)));
27213        let is_cross_join_dialect = matches!(
27214            self.config.dialect,
27215            Some(DialectType::BigQuery)
27216                | Some(DialectType::Hive)
27217                | Some(DialectType::Spark)
27218                | Some(DialectType::Databricks)
27219                | Some(DialectType::SQLite)
27220                | Some(DialectType::ClickHouse)
27221        );
27222        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27223            && self.config.source_dialect == self.config.dialect;
27224        let source_is_cross_join_dialect2 = matches!(
27225            self.config.source_dialect,
27226            Some(DialectType::BigQuery)
27227                | Some(DialectType::Hive)
27228                | Some(DialectType::Spark)
27229                | Some(DialectType::Databricks)
27230                | Some(DialectType::SQLite)
27231                | Some(DialectType::ClickHouse)
27232        );
27233        let use_cross_join = !has_tablesample
27234            && is_cross_join_dialect
27235            && (source_is_same_as_target2
27236                || source_is_cross_join_dialect2
27237                || self.config.source_dialect.is_none());
27238
27239        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27240        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27241
27242        for (i, expr) in e.expressions.iter().enumerate() {
27243            if i > 0 {
27244                if use_cross_join {
27245                    self.write(" CROSS JOIN ");
27246                } else {
27247                    self.write(", ");
27248                }
27249            }
27250            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27251                self.write("(");
27252                self.generate_expression(expr)?;
27253                self.write(")");
27254            } else {
27255                self.generate_expression(expr)?;
27256            }
27257        }
27258        Ok(())
27259    }
27260
27261    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27262        // FROM_BASE(this, expression) - convert from base N
27263        self.write_keyword("FROM_BASE");
27264        self.write("(");
27265        self.generate_expression(&e.this)?;
27266        self.write(", ");
27267        self.generate_expression(&e.expression)?;
27268        self.write(")");
27269        Ok(())
27270    }
27271
27272    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27273        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27274        self.generate_expression(&e.this)?;
27275        if let Some(zone) = &e.zone {
27276            self.write_space();
27277            self.write_keyword("AT TIME ZONE");
27278            self.write_space();
27279            self.generate_expression(zone)?;
27280            self.write_space();
27281            self.write_keyword("AT TIME ZONE");
27282            self.write(" 'UTC'");
27283        }
27284        Ok(())
27285    }
27286
27287    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27288        // GAP_FILL(this, ts_column, bucket_width, ...)
27289        self.write_keyword("GAP_FILL");
27290        self.write("(");
27291        self.generate_expression(&e.this)?;
27292        if let Some(ts_column) = &e.ts_column {
27293            self.write(", ");
27294            self.generate_expression(ts_column)?;
27295        }
27296        if let Some(bucket_width) = &e.bucket_width {
27297            self.write(", ");
27298            self.generate_expression(bucket_width)?;
27299        }
27300        if let Some(partitioning_columns) = &e.partitioning_columns {
27301            self.write(", ");
27302            self.generate_expression(partitioning_columns)?;
27303        }
27304        if let Some(value_columns) = &e.value_columns {
27305            self.write(", ");
27306            self.generate_expression(value_columns)?;
27307        }
27308        self.write(")");
27309        Ok(())
27310    }
27311
27312    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27313        // GENERATE_DATE_ARRAY(start, end, step)
27314        self.write_keyword("GENERATE_DATE_ARRAY");
27315        self.write("(");
27316        let mut first = true;
27317        if let Some(start) = &e.start {
27318            self.generate_expression(start)?;
27319            first = false;
27320        }
27321        if let Some(end) = &e.end {
27322            if !first {
27323                self.write(", ");
27324            }
27325            self.generate_expression(end)?;
27326            first = false;
27327        }
27328        if let Some(step) = &e.step {
27329            if !first {
27330                self.write(", ");
27331            }
27332            self.generate_expression(step)?;
27333        }
27334        self.write(")");
27335        Ok(())
27336    }
27337
27338    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27339        // ML.GENERATE_EMBEDDING(model, content, params)
27340        self.write_keyword("ML.GENERATE_EMBEDDING");
27341        self.write("(");
27342        self.generate_expression(&e.this)?;
27343        self.write(", ");
27344        self.generate_expression(&e.expression)?;
27345        if let Some(params) = &e.params_struct {
27346            self.write(", ");
27347            self.generate_expression(params)?;
27348        }
27349        self.write(")");
27350        Ok(())
27351    }
27352
27353    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27354        // Dialect-specific function name
27355        let fn_name = match self.config.dialect {
27356            Some(DialectType::Presto)
27357            | Some(DialectType::Trino)
27358            | Some(DialectType::Athena)
27359            | Some(DialectType::Spark)
27360            | Some(DialectType::Databricks)
27361            | Some(DialectType::Hive) => "SEQUENCE",
27362            _ => "GENERATE_SERIES",
27363        };
27364        self.write_keyword(fn_name);
27365        self.write("(");
27366        let mut first = true;
27367        if let Some(start) = &e.start {
27368            self.generate_expression(start)?;
27369            first = false;
27370        }
27371        if let Some(end) = &e.end {
27372            if !first {
27373                self.write(", ");
27374            }
27375            self.generate_expression(end)?;
27376            first = false;
27377        }
27378        if let Some(step) = &e.step {
27379            if !first {
27380                self.write(", ");
27381            }
27382            // For Presto/Trino: convert WEEK intervals to DAY multiples
27383            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27384            if matches!(
27385                self.config.dialect,
27386                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27387            ) {
27388                if let Some(converted) = self.convert_week_interval_to_day(step) {
27389                    self.generate_expression(&converted)?;
27390                } else {
27391                    self.generate_expression(step)?;
27392                }
27393            } else {
27394                self.generate_expression(step)?;
27395            }
27396        }
27397        self.write(")");
27398        Ok(())
27399    }
27400
27401    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27402    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27403    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27404        use crate::expressions::*;
27405        if let Expression::Interval(ref iv) = expr {
27406            // Check for structured WEEK unit
27407            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27408                unit: IntervalUnit::Week,
27409                ..
27410            }) = &iv.unit
27411            {
27412                // Value is in iv.this
27413                let count = match &iv.this {
27414                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27415                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27416                    _ => return None,
27417                };
27418                (true, count)
27419            } else if iv.unit.is_none() {
27420                // Check for string-encoded interval like "1 WEEK"
27421                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27422                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27423                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27424                        (true, parts[0].to_string())
27425                    } else {
27426                        (false, String::new())
27427                    }
27428                } else {
27429                    (false, String::new())
27430                }
27431            } else {
27432                (false, String::new())
27433            };
27434
27435            if is_week {
27436                // Build: (N * INTERVAL '7' DAY)
27437                let count_expr = Expression::Literal(Literal::Number(count_str));
27438                let day_interval = Expression::Interval(Box::new(Interval {
27439                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27440                    unit: Some(IntervalUnitSpec::Simple {
27441                        unit: IntervalUnit::Day,
27442                        use_plural: false,
27443                    }),
27444                }));
27445                let mul = Expression::Mul(Box::new(BinaryOp {
27446                    left: count_expr,
27447                    right: day_interval,
27448                    left_comments: vec![],
27449                    operator_comments: vec![],
27450                    trailing_comments: vec![],
27451                }));
27452                return Some(Expression::Paren(Box::new(Paren {
27453                    this: mul,
27454                    trailing_comments: vec![],
27455                })));
27456            }
27457        }
27458        None
27459    }
27460
27461    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27462        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27463        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27464        self.write("(");
27465        let mut first = true;
27466        if let Some(start) = &e.start {
27467            self.generate_expression(start)?;
27468            first = false;
27469        }
27470        if let Some(end) = &e.end {
27471            if !first {
27472                self.write(", ");
27473            }
27474            self.generate_expression(end)?;
27475            first = false;
27476        }
27477        if let Some(step) = &e.step {
27478            if !first {
27479                self.write(", ");
27480            }
27481            self.generate_expression(step)?;
27482        }
27483        self.write(")");
27484        Ok(())
27485    }
27486
27487    fn generate_generated_as_identity_column_constraint(
27488        &mut self,
27489        e: &GeneratedAsIdentityColumnConstraint,
27490    ) -> Result<()> {
27491        use crate::dialects::DialectType;
27492
27493        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27494        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27495            self.write_keyword("AUTOINCREMENT");
27496            if let Some(start) = &e.start {
27497                self.write_keyword(" START ");
27498                self.generate_expression(start)?;
27499            }
27500            if let Some(increment) = &e.increment {
27501                self.write_keyword(" INCREMENT ");
27502                self.generate_expression(increment)?;
27503            }
27504            return Ok(());
27505        }
27506
27507        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27508        self.write_keyword("GENERATED");
27509        if let Some(this) = &e.this {
27510            // Check if it's a truthy boolean expression
27511            if let Expression::Boolean(b) = this.as_ref() {
27512                if b.value {
27513                    self.write_keyword(" ALWAYS");
27514                } else {
27515                    self.write_keyword(" BY DEFAULT");
27516                    if e.on_null.is_some() {
27517                        self.write_keyword(" ON NULL");
27518                    }
27519                }
27520            } else {
27521                self.write_keyword(" ALWAYS");
27522            }
27523        }
27524        self.write_keyword(" AS IDENTITY");
27525        // Add sequence options if any
27526        let has_options = e.start.is_some()
27527            || e.increment.is_some()
27528            || e.minvalue.is_some()
27529            || e.maxvalue.is_some();
27530        if has_options {
27531            self.write(" (");
27532            let mut first = true;
27533            if let Some(start) = &e.start {
27534                self.write_keyword("START WITH ");
27535                self.generate_expression(start)?;
27536                first = false;
27537            }
27538            if let Some(increment) = &e.increment {
27539                if !first {
27540                    self.write(" ");
27541                }
27542                self.write_keyword("INCREMENT BY ");
27543                self.generate_expression(increment)?;
27544                first = false;
27545            }
27546            if let Some(minvalue) = &e.minvalue {
27547                if !first {
27548                    self.write(" ");
27549                }
27550                self.write_keyword("MINVALUE ");
27551                self.generate_expression(minvalue)?;
27552                first = false;
27553            }
27554            if let Some(maxvalue) = &e.maxvalue {
27555                if !first {
27556                    self.write(" ");
27557                }
27558                self.write_keyword("MAXVALUE ");
27559                self.generate_expression(maxvalue)?;
27560            }
27561            self.write(")");
27562        }
27563        Ok(())
27564    }
27565
27566    fn generate_generated_as_row_column_constraint(
27567        &mut self,
27568        e: &GeneratedAsRowColumnConstraint,
27569    ) -> Result<()> {
27570        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27571        self.write_keyword("GENERATED ALWAYS AS ROW ");
27572        if e.start.is_some() {
27573            self.write_keyword("START");
27574        } else {
27575            self.write_keyword("END");
27576        }
27577        if e.hidden.is_some() {
27578            self.write_keyword(" HIDDEN");
27579        }
27580        Ok(())
27581    }
27582
27583    fn generate_get(&mut self, e: &Get) -> Result<()> {
27584        // GET this target properties
27585        self.write_keyword("GET");
27586        self.write_space();
27587        self.generate_expression(&e.this)?;
27588        if let Some(target) = &e.target {
27589            self.write_space();
27590            self.generate_expression(target)?;
27591        }
27592        for prop in &e.properties {
27593            self.write_space();
27594            self.generate_expression(prop)?;
27595        }
27596        Ok(())
27597    }
27598
27599    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27600        // GetExtract generates bracket access: this[expression]
27601        self.generate_expression(&e.this)?;
27602        self.write("[");
27603        self.generate_expression(&e.expression)?;
27604        self.write("]");
27605        Ok(())
27606    }
27607
27608    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27609        // GETBIT(this, expression) or GET_BIT(this, expression)
27610        self.write_keyword("GETBIT");
27611        self.write("(");
27612        self.generate_expression(&e.this)?;
27613        self.write(", ");
27614        self.generate_expression(&e.expression)?;
27615        self.write(")");
27616        Ok(())
27617    }
27618
27619    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27620        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27621        if e.is_role {
27622            self.write_keyword("ROLE");
27623            self.write_space();
27624        } else if e.is_group {
27625            self.write_keyword("GROUP");
27626            self.write_space();
27627        }
27628        self.write(&e.name.name);
27629        Ok(())
27630    }
27631
27632    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27633        // privilege(columns) or just privilege
27634        self.generate_expression(&e.this)?;
27635        if !e.expressions.is_empty() {
27636            self.write("(");
27637            for (i, expr) in e.expressions.iter().enumerate() {
27638                if i > 0 {
27639                    self.write(", ");
27640                }
27641                self.generate_expression(expr)?;
27642            }
27643            self.write(")");
27644        }
27645        Ok(())
27646    }
27647
27648    fn generate_group(&mut self, e: &Group) -> Result<()> {
27649        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27650        self.write_keyword("GROUP BY");
27651        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27652        match e.all {
27653            Some(true) => {
27654                self.write_space();
27655                self.write_keyword("ALL");
27656            }
27657            Some(false) => {
27658                self.write_space();
27659                self.write_keyword("DISTINCT");
27660            }
27661            None => {}
27662        }
27663        if !e.expressions.is_empty() {
27664            self.write_space();
27665            for (i, expr) in e.expressions.iter().enumerate() {
27666                if i > 0 {
27667                    self.write(", ");
27668                }
27669                self.generate_expression(expr)?;
27670            }
27671        }
27672        // Handle CUBE, ROLLUP, GROUPING SETS
27673        if let Some(cube) = &e.cube {
27674            if !e.expressions.is_empty() {
27675                self.write(", ");
27676            } else {
27677                self.write_space();
27678            }
27679            self.generate_expression(cube)?;
27680        }
27681        if let Some(rollup) = &e.rollup {
27682            if !e.expressions.is_empty() || e.cube.is_some() {
27683                self.write(", ");
27684            } else {
27685                self.write_space();
27686            }
27687            self.generate_expression(rollup)?;
27688        }
27689        if let Some(grouping_sets) = &e.grouping_sets {
27690            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27691                self.write(", ");
27692            } else {
27693                self.write_space();
27694            }
27695            self.generate_expression(grouping_sets)?;
27696        }
27697        if let Some(totals) = &e.totals {
27698            self.write_space();
27699            self.write_keyword("WITH TOTALS");
27700            self.generate_expression(totals)?;
27701        }
27702        Ok(())
27703    }
27704
27705    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27706        // GROUP BY expressions
27707        self.write_keyword("GROUP BY");
27708        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27709        match e.all {
27710            Some(true) => {
27711                self.write_space();
27712                self.write_keyword("ALL");
27713            }
27714            Some(false) => {
27715                self.write_space();
27716                self.write_keyword("DISTINCT");
27717            }
27718            None => {}
27719        }
27720
27721        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27722        // These are represented as Cube/Rollup expressions with empty expressions at the end
27723        let mut trailing_cube = false;
27724        let mut trailing_rollup = false;
27725        let mut regular_expressions: Vec<&Expression> = Vec::new();
27726
27727        for expr in &e.expressions {
27728            match expr {
27729                Expression::Cube(c) if c.expressions.is_empty() => {
27730                    trailing_cube = true;
27731                }
27732                Expression::Rollup(r) if r.expressions.is_empty() => {
27733                    trailing_rollup = true;
27734                }
27735                _ => {
27736                    regular_expressions.push(expr);
27737                }
27738            }
27739        }
27740
27741        // In pretty mode, put columns on separate lines
27742        if self.config.pretty {
27743            self.write_newline();
27744            self.indent_level += 1;
27745            for (i, expr) in regular_expressions.iter().enumerate() {
27746                if i > 0 {
27747                    self.write(",");
27748                    self.write_newline();
27749                }
27750                self.write_indent();
27751                self.generate_expression(expr)?;
27752            }
27753            self.indent_level -= 1;
27754        } else {
27755            self.write_space();
27756            for (i, expr) in regular_expressions.iter().enumerate() {
27757                if i > 0 {
27758                    self.write(", ");
27759                }
27760                self.generate_expression(expr)?;
27761            }
27762        }
27763
27764        // Output trailing WITH CUBE or WITH ROLLUP
27765        if trailing_cube {
27766            self.write_space();
27767            self.write_keyword("WITH CUBE");
27768        } else if trailing_rollup {
27769            self.write_space();
27770            self.write_keyword("WITH ROLLUP");
27771        }
27772
27773        // ClickHouse: WITH TOTALS
27774        if e.totals {
27775            self.write_space();
27776            self.write_keyword("WITH TOTALS");
27777        }
27778
27779        Ok(())
27780    }
27781
27782    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27783        // GROUPING(col1, col2, ...)
27784        self.write_keyword("GROUPING");
27785        self.write("(");
27786        for (i, expr) in e.expressions.iter().enumerate() {
27787            if i > 0 {
27788                self.write(", ");
27789            }
27790            self.generate_expression(expr)?;
27791        }
27792        self.write(")");
27793        Ok(())
27794    }
27795
27796    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27797        // GROUPING_ID(col1, col2, ...)
27798        self.write_keyword("GROUPING_ID");
27799        self.write("(");
27800        for (i, expr) in e.expressions.iter().enumerate() {
27801            if i > 0 {
27802                self.write(", ");
27803            }
27804            self.generate_expression(expr)?;
27805        }
27806        self.write(")");
27807        Ok(())
27808    }
27809
27810    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27811        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27812        self.write_keyword("GROUPING SETS");
27813        self.write(" (");
27814        for (i, expr) in e.expressions.iter().enumerate() {
27815            if i > 0 {
27816                self.write(", ");
27817            }
27818            self.generate_expression(expr)?;
27819        }
27820        self.write(")");
27821        Ok(())
27822    }
27823
27824    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27825        // HASH_AGG(this, expressions...)
27826        self.write_keyword("HASH_AGG");
27827        self.write("(");
27828        self.generate_expression(&e.this)?;
27829        for expr in &e.expressions {
27830            self.write(", ");
27831            self.generate_expression(expr)?;
27832        }
27833        self.write(")");
27834        Ok(())
27835    }
27836
27837    fn generate_having(&mut self, e: &Having) -> Result<()> {
27838        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27839        self.write_keyword("HAVING");
27840        self.write_space();
27841        self.generate_expression(&e.this)?;
27842        Ok(())
27843    }
27844
27845    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27846        // Python: this HAVING MAX|MIN expression
27847        self.generate_expression(&e.this)?;
27848        self.write_space();
27849        self.write_keyword("HAVING");
27850        self.write_space();
27851        if e.max.is_some() {
27852            self.write_keyword("MAX");
27853        } else {
27854            self.write_keyword("MIN");
27855        }
27856        self.write_space();
27857        self.generate_expression(&e.expression)?;
27858        Ok(())
27859    }
27860
27861    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27862        use crate::dialects::DialectType;
27863        // DuckDB: convert dollar-tagged strings to single-quoted
27864        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27865            // Extract the string content and output as single-quoted
27866            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27867                return self.generate_string_literal(s);
27868            }
27869        }
27870        // PostgreSQL: preserve dollar-quoting
27871        if matches!(
27872            self.config.dialect,
27873            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27874        ) {
27875            self.write("$");
27876            if let Some(tag) = &e.tag {
27877                self.generate_expression(tag)?;
27878            }
27879            self.write("$");
27880            self.generate_expression(&e.this)?;
27881            self.write("$");
27882            if let Some(tag) = &e.tag {
27883                self.generate_expression(tag)?;
27884            }
27885            self.write("$");
27886            return Ok(());
27887        }
27888        // Default: output as dollar-tagged
27889        self.write("$");
27890        if let Some(tag) = &e.tag {
27891            self.generate_expression(tag)?;
27892        }
27893        self.write("$");
27894        self.generate_expression(&e.this)?;
27895        self.write("$");
27896        if let Some(tag) = &e.tag {
27897            self.generate_expression(tag)?;
27898        }
27899        self.write("$");
27900        Ok(())
27901    }
27902
27903    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27904        // HEX_ENCODE(this)
27905        self.write_keyword("HEX_ENCODE");
27906        self.write("(");
27907        self.generate_expression(&e.this)?;
27908        self.write(")");
27909        Ok(())
27910    }
27911
27912    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27913        // Python: this (kind => expression)
27914        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27915        match e.this.as_ref() {
27916            Expression::Identifier(id) => self.write(&id.name),
27917            other => self.generate_expression(other)?,
27918        }
27919        self.write(" (");
27920        self.write(&e.kind);
27921        self.write(" => ");
27922        self.generate_expression(&e.expression)?;
27923        self.write(")");
27924        Ok(())
27925    }
27926
27927    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27928        // HLL(this, expressions...)
27929        self.write_keyword("HLL");
27930        self.write("(");
27931        self.generate_expression(&e.this)?;
27932        for expr in &e.expressions {
27933            self.write(", ");
27934            self.generate_expression(expr)?;
27935        }
27936        self.write(")");
27937        Ok(())
27938    }
27939
27940    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27941        // Python: IN|OUT|IN OUT
27942        if e.input_.is_some() && e.output.is_some() {
27943            self.write_keyword("IN OUT");
27944        } else if e.input_.is_some() {
27945            self.write_keyword("IN");
27946        } else if e.output.is_some() {
27947            self.write_keyword("OUT");
27948        }
27949        Ok(())
27950    }
27951
27952    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27953        // Python: INCLUDE this [column_def] [AS alias]
27954        self.write_keyword("INCLUDE");
27955        self.write_space();
27956        self.generate_expression(&e.this)?;
27957        if let Some(column_def) = &e.column_def {
27958            self.write_space();
27959            self.generate_expression(column_def)?;
27960        }
27961        if let Some(alias) = &e.alias {
27962            self.write_space();
27963            self.write_keyword("AS");
27964            self.write_space();
27965            self.write(alias);
27966        }
27967        Ok(())
27968    }
27969
27970    fn generate_index(&mut self, e: &Index) -> Result<()> {
27971        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27972        if e.unique {
27973            self.write_keyword("UNIQUE");
27974            self.write_space();
27975        }
27976        if e.primary.is_some() {
27977            self.write_keyword("PRIMARY");
27978            self.write_space();
27979        }
27980        if e.amp.is_some() {
27981            self.write_keyword("AMP");
27982            self.write_space();
27983        }
27984        if e.table.is_none() {
27985            self.write_keyword("INDEX");
27986            self.write_space();
27987        }
27988        if let Some(name) = &e.this {
27989            self.generate_expression(name)?;
27990            self.write_space();
27991        }
27992        if let Some(table) = &e.table {
27993            self.write_keyword("ON");
27994            self.write_space();
27995            self.generate_expression(table)?;
27996        }
27997        if !e.params.is_empty() {
27998            self.write("(");
27999            for (i, param) in e.params.iter().enumerate() {
28000                if i > 0 {
28001                    self.write(", ");
28002                }
28003                self.generate_expression(param)?;
28004            }
28005            self.write(")");
28006        }
28007        Ok(())
28008    }
28009
28010    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
28011        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
28012        if let Some(kind) = &e.kind {
28013            self.write(kind);
28014            self.write_space();
28015        }
28016        self.write_keyword("INDEX");
28017        if let Some(this) = &e.this {
28018            self.write_space();
28019            self.generate_expression(this)?;
28020        }
28021        if let Some(index_type) = &e.index_type {
28022            self.write_space();
28023            self.write_keyword("USING");
28024            self.write_space();
28025            self.generate_expression(index_type)?;
28026        }
28027        if !e.expressions.is_empty() {
28028            self.write(" (");
28029            for (i, expr) in e.expressions.iter().enumerate() {
28030                if i > 0 {
28031                    self.write(", ");
28032                }
28033                self.generate_expression(expr)?;
28034            }
28035            self.write(")");
28036        }
28037        for opt in &e.options {
28038            self.write_space();
28039            self.generate_expression(opt)?;
28040        }
28041        Ok(())
28042    }
28043
28044    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
28045        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
28046        if let Some(key_block_size) = &e.key_block_size {
28047            self.write_keyword("KEY_BLOCK_SIZE");
28048            self.write(" = ");
28049            self.generate_expression(key_block_size)?;
28050        } else if let Some(using) = &e.using {
28051            self.write_keyword("USING");
28052            self.write_space();
28053            self.generate_expression(using)?;
28054        } else if let Some(parser) = &e.parser {
28055            self.write_keyword("WITH PARSER");
28056            self.write_space();
28057            self.generate_expression(parser)?;
28058        } else if let Some(comment) = &e.comment {
28059            self.write_keyword("COMMENT");
28060            self.write_space();
28061            self.generate_expression(comment)?;
28062        } else if let Some(visible) = &e.visible {
28063            self.generate_expression(visible)?;
28064        } else if let Some(engine_attr) = &e.engine_attr {
28065            self.write_keyword("ENGINE_ATTRIBUTE");
28066            self.write(" = ");
28067            self.generate_expression(engine_attr)?;
28068        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
28069            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
28070            self.write(" = ");
28071            self.generate_expression(secondary_engine_attr)?;
28072        }
28073        Ok(())
28074    }
28075
28076    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28077        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28078        if let Some(using) = &e.using {
28079            self.write_keyword("USING");
28080            self.write_space();
28081            self.generate_expression(using)?;
28082        }
28083        if !e.columns.is_empty() {
28084            self.write("(");
28085            for (i, col) in e.columns.iter().enumerate() {
28086                if i > 0 {
28087                    self.write(", ");
28088                }
28089                self.generate_expression(col)?;
28090            }
28091            self.write(")");
28092        }
28093        if let Some(partition_by) = &e.partition_by {
28094            self.write_space();
28095            self.write_keyword("PARTITION BY");
28096            self.write_space();
28097            self.generate_expression(partition_by)?;
28098        }
28099        if let Some(where_) = &e.where_ {
28100            self.write_space();
28101            self.generate_expression(where_)?;
28102        }
28103        if let Some(include) = &e.include {
28104            self.write_space();
28105            self.write_keyword("INCLUDE");
28106            self.write(" (");
28107            self.generate_expression(include)?;
28108            self.write(")");
28109        }
28110        if let Some(with_storage) = &e.with_storage {
28111            self.write_space();
28112            self.write_keyword("WITH");
28113            self.write(" (");
28114            self.generate_expression(with_storage)?;
28115            self.write(")");
28116        }
28117        if let Some(tablespace) = &e.tablespace {
28118            self.write_space();
28119            self.write_keyword("USING INDEX TABLESPACE");
28120            self.write_space();
28121            self.generate_expression(tablespace)?;
28122        }
28123        Ok(())
28124    }
28125
28126    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28127        // Python: this INDEX [FOR target] (expressions)
28128        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28129        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28130        if let Expression::Identifier(id) = &*e.this {
28131            self.write_keyword(&id.name);
28132        } else {
28133            self.generate_expression(&e.this)?;
28134        }
28135        self.write_space();
28136        self.write_keyword("INDEX");
28137        if let Some(target) = &e.target {
28138            self.write_space();
28139            self.write_keyword("FOR");
28140            self.write_space();
28141            if let Expression::Identifier(id) = &**target {
28142                self.write_keyword(&id.name);
28143            } else {
28144                self.generate_expression(target)?;
28145            }
28146        }
28147        // Always output parentheses (even if empty, e.g. USE INDEX ())
28148        self.write(" (");
28149        for (i, expr) in e.expressions.iter().enumerate() {
28150            if i > 0 {
28151                self.write(", ");
28152            }
28153            self.generate_expression(expr)?;
28154        }
28155        self.write(")");
28156        Ok(())
28157    }
28158
28159    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28160        // INHERITS (table1, table2, ...)
28161        self.write_keyword("INHERITS");
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        self.write(")");
28170        Ok(())
28171    }
28172
28173    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28174        // INPUT(model)
28175        self.write_keyword("INPUT");
28176        self.write("(");
28177        self.generate_expression(&e.this)?;
28178        self.write(")");
28179        Ok(())
28180    }
28181
28182    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28183        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28184        if let Some(input_format) = &e.input_format {
28185            self.write_keyword("INPUTFORMAT");
28186            self.write_space();
28187            self.generate_expression(input_format)?;
28188        }
28189        if let Some(output_format) = &e.output_format {
28190            if e.input_format.is_some() {
28191                self.write(" ");
28192            }
28193            self.write_keyword("OUTPUTFORMAT");
28194            self.write_space();
28195            self.generate_expression(output_format)?;
28196        }
28197        Ok(())
28198    }
28199
28200    fn generate_install(&mut self, e: &Install) -> Result<()> {
28201        // [FORCE] INSTALL extension [FROM source]
28202        if e.force.is_some() {
28203            self.write_keyword("FORCE");
28204            self.write_space();
28205        }
28206        self.write_keyword("INSTALL");
28207        self.write_space();
28208        self.generate_expression(&e.this)?;
28209        if let Some(from) = &e.from_ {
28210            self.write_space();
28211            self.write_keyword("FROM");
28212            self.write_space();
28213            self.generate_expression(from)?;
28214        }
28215        Ok(())
28216    }
28217
28218    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28219        // INTERVAL 'expression' unit
28220        self.write_keyword("INTERVAL");
28221        self.write_space();
28222        // When a unit is specified and the expression is a number,
28223        self.generate_expression(&e.expression)?;
28224        if let Some(unit) = &e.unit {
28225            self.write_space();
28226            self.write(unit);
28227        }
28228        Ok(())
28229    }
28230
28231    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28232        // unit TO unit (e.g., HOUR TO SECOND)
28233        self.write(&format!("{:?}", e.this).to_uppercase());
28234        self.write_space();
28235        self.write_keyword("TO");
28236        self.write_space();
28237        self.write(&format!("{:?}", e.expression).to_uppercase());
28238        Ok(())
28239    }
28240
28241    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28242        // INTO [TEMPORARY|UNLOGGED] table
28243        self.write_keyword("INTO");
28244        if e.temporary {
28245            self.write_keyword(" TEMPORARY");
28246        }
28247        if e.unlogged.is_some() {
28248            self.write_keyword(" UNLOGGED");
28249        }
28250        if let Some(this) = &e.this {
28251            self.write_space();
28252            self.generate_expression(this)?;
28253        }
28254        if !e.expressions.is_empty() {
28255            self.write(" (");
28256            for (i, expr) in e.expressions.iter().enumerate() {
28257                if i > 0 {
28258                    self.write(", ");
28259                }
28260                self.generate_expression(expr)?;
28261            }
28262            self.write(")");
28263        }
28264        Ok(())
28265    }
28266
28267    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28268        // Python: this expression (e.g., _utf8 'string')
28269        self.generate_expression(&e.this)?;
28270        self.write_space();
28271        self.generate_expression(&e.expression)?;
28272        Ok(())
28273    }
28274
28275    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28276        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28277        self.write_keyword("WITH");
28278        if e.no.is_some() {
28279            self.write_keyword(" NO");
28280        }
28281        if e.concurrent.is_some() {
28282            self.write_keyword(" CONCURRENT");
28283        }
28284        self.write_keyword(" ISOLATED LOADING");
28285        if let Some(target) = &e.target {
28286            self.write_space();
28287            self.generate_expression(target)?;
28288        }
28289        Ok(())
28290    }
28291
28292    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28293        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28294        self.write_keyword("JSON");
28295        if let Some(this) = &e.this {
28296            self.write_space();
28297            self.generate_expression(this)?;
28298        }
28299        if let Some(with_) = &e.with_ {
28300            // Check if it's a truthy boolean
28301            if let Expression::Boolean(b) = with_.as_ref() {
28302                if b.value {
28303                    self.write_keyword(" WITH");
28304                } else {
28305                    self.write_keyword(" WITHOUT");
28306                }
28307            }
28308        }
28309        if e.unique {
28310            self.write_keyword(" UNIQUE KEYS");
28311        }
28312        Ok(())
28313    }
28314
28315    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28316        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28317        self.write_keyword("JSON_ARRAY");
28318        self.write("(");
28319        for (i, expr) in e.expressions.iter().enumerate() {
28320            if i > 0 {
28321                self.write(", ");
28322            }
28323            self.generate_expression(expr)?;
28324        }
28325        if let Some(null_handling) = &e.null_handling {
28326            self.write_space();
28327            self.generate_expression(null_handling)?;
28328        }
28329        if let Some(return_type) = &e.return_type {
28330            self.write_space();
28331            self.write_keyword("RETURNING");
28332            self.write_space();
28333            self.generate_expression(return_type)?;
28334        }
28335        if e.strict.is_some() {
28336            self.write_space();
28337            self.write_keyword("STRICT");
28338        }
28339        self.write(")");
28340        Ok(())
28341    }
28342
28343    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28344        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28345        self.write_keyword("JSON_ARRAYAGG");
28346        self.write("(");
28347        self.generate_expression(&e.this)?;
28348        if let Some(order) = &e.order {
28349            self.write_space();
28350            // Order is stored as an OrderBy expression
28351            if let Expression::OrderBy(ob) = order.as_ref() {
28352                self.write_keyword("ORDER BY");
28353                self.write_space();
28354                for (i, ord) in ob.expressions.iter().enumerate() {
28355                    if i > 0 {
28356                        self.write(", ");
28357                    }
28358                    self.generate_ordered(ord)?;
28359                }
28360            } else {
28361                // Fallback: generate the expression directly
28362                self.generate_expression(order)?;
28363            }
28364        }
28365        if let Some(null_handling) = &e.null_handling {
28366            self.write_space();
28367            self.generate_expression(null_handling)?;
28368        }
28369        if let Some(return_type) = &e.return_type {
28370            self.write_space();
28371            self.write_keyword("RETURNING");
28372            self.write_space();
28373            self.generate_expression(return_type)?;
28374        }
28375        if e.strict.is_some() {
28376            self.write_space();
28377            self.write_keyword("STRICT");
28378        }
28379        self.write(")");
28380        Ok(())
28381    }
28382
28383    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28384        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28385        self.write_keyword("JSON_OBJECTAGG");
28386        self.write("(");
28387        for (i, expr) in e.expressions.iter().enumerate() {
28388            if i > 0 {
28389                self.write(", ");
28390            }
28391            self.generate_expression(expr)?;
28392        }
28393        if let Some(null_handling) = &e.null_handling {
28394            self.write_space();
28395            self.generate_expression(null_handling)?;
28396        }
28397        if let Some(unique_keys) = &e.unique_keys {
28398            self.write_space();
28399            if let Expression::Boolean(b) = unique_keys.as_ref() {
28400                if b.value {
28401                    self.write_keyword("WITH UNIQUE KEYS");
28402                } else {
28403                    self.write_keyword("WITHOUT UNIQUE KEYS");
28404                }
28405            }
28406        }
28407        if let Some(return_type) = &e.return_type {
28408            self.write_space();
28409            self.write_keyword("RETURNING");
28410            self.write_space();
28411            self.generate_expression(return_type)?;
28412        }
28413        self.write(")");
28414        Ok(())
28415    }
28416
28417    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28418        // JSON_ARRAY_APPEND(this, path, value, ...)
28419        self.write_keyword("JSON_ARRAY_APPEND");
28420        self.write("(");
28421        self.generate_expression(&e.this)?;
28422        for expr in &e.expressions {
28423            self.write(", ");
28424            self.generate_expression(expr)?;
28425        }
28426        self.write(")");
28427        Ok(())
28428    }
28429
28430    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28431        // JSON_ARRAY_CONTAINS(this, expression)
28432        self.write_keyword("JSON_ARRAY_CONTAINS");
28433        self.write("(");
28434        self.generate_expression(&e.this)?;
28435        self.write(", ");
28436        self.generate_expression(&e.expression)?;
28437        self.write(")");
28438        Ok(())
28439    }
28440
28441    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28442        // JSON_ARRAY_INSERT(this, path, value, ...)
28443        self.write_keyword("JSON_ARRAY_INSERT");
28444        self.write("(");
28445        self.generate_expression(&e.this)?;
28446        for expr in &e.expressions {
28447            self.write(", ");
28448            self.generate_expression(expr)?;
28449        }
28450        self.write(")");
28451        Ok(())
28452    }
28453
28454    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28455        // JSONB_EXISTS(this, path)
28456        self.write_keyword("JSONB_EXISTS");
28457        self.write("(");
28458        self.generate_expression(&e.this)?;
28459        if let Some(path) = &e.path {
28460            self.write(", ");
28461            self.generate_expression(path)?;
28462        }
28463        self.write(")");
28464        Ok(())
28465    }
28466
28467    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28468        // JSONB_EXTRACT_SCALAR(this, expression)
28469        self.write_keyword("JSONB_EXTRACT_SCALAR");
28470        self.write("(");
28471        self.generate_expression(&e.this)?;
28472        self.write(", ");
28473        self.generate_expression(&e.expression)?;
28474        self.write(")");
28475        Ok(())
28476    }
28477
28478    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28479        // JSONB_OBJECT_AGG(this, expression)
28480        self.write_keyword("JSONB_OBJECT_AGG");
28481        self.write("(");
28482        self.generate_expression(&e.this)?;
28483        self.write(", ");
28484        self.generate_expression(&e.expression)?;
28485        self.write(")");
28486        Ok(())
28487    }
28488
28489    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28490        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28491        if let Some(nested_schema) = &e.nested_schema {
28492            self.write_keyword("NESTED");
28493            if let Some(path) = &e.path {
28494                self.write_space();
28495                self.write_keyword("PATH");
28496                self.write_space();
28497                self.generate_expression(path)?;
28498            }
28499            self.write_space();
28500            self.generate_expression(nested_schema)?;
28501        } else {
28502            if let Some(this) = &e.this {
28503                self.generate_expression(this)?;
28504            }
28505            if let Some(kind) = &e.kind {
28506                self.write_space();
28507                self.write(kind);
28508            }
28509            if let Some(path) = &e.path {
28510                self.write_space();
28511                self.write_keyword("PATH");
28512                self.write_space();
28513                self.generate_expression(path)?;
28514            }
28515            if e.ordinality.is_some() {
28516                self.write_keyword(" FOR ORDINALITY");
28517            }
28518        }
28519        Ok(())
28520    }
28521
28522    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28523        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28524        self.write_keyword("JSON_EXISTS");
28525        self.write("(");
28526        self.generate_expression(&e.this)?;
28527        if let Some(path) = &e.path {
28528            self.write(", ");
28529            self.generate_expression(path)?;
28530        }
28531        if let Some(passing) = &e.passing {
28532            self.write_space();
28533            self.write_keyword("PASSING");
28534            self.write_space();
28535            self.generate_expression(passing)?;
28536        }
28537        if let Some(on_condition) = &e.on_condition {
28538            self.write_space();
28539            self.generate_expression(on_condition)?;
28540        }
28541        self.write(")");
28542        Ok(())
28543    }
28544
28545    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28546        self.generate_expression(&e.this)?;
28547        self.write(".:");
28548        self.generate_data_type(&e.to)?;
28549        Ok(())
28550    }
28551
28552    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28553        // JSON_EXTRACT_ARRAY(this, expression)
28554        self.write_keyword("JSON_EXTRACT_ARRAY");
28555        self.write("(");
28556        self.generate_expression(&e.this)?;
28557        if let Some(expr) = &e.expression {
28558            self.write(", ");
28559            self.generate_expression(expr)?;
28560        }
28561        self.write(")");
28562        Ok(())
28563    }
28564
28565    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28566        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28567        if let Some(option) = &e.option {
28568            self.generate_expression(option)?;
28569            self.write_space();
28570        }
28571        self.write_keyword("QUOTES");
28572        if e.scalar.is_some() {
28573            self.write_keyword(" SCALAR_ONLY");
28574        }
28575        Ok(())
28576    }
28577
28578    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28579        // JSON_EXTRACT_SCALAR(this, expression)
28580        self.write_keyword("JSON_EXTRACT_SCALAR");
28581        self.write("(");
28582        self.generate_expression(&e.this)?;
28583        self.write(", ");
28584        self.generate_expression(&e.expression)?;
28585        self.write(")");
28586        Ok(())
28587    }
28588
28589    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28590        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28591        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28592        // Otherwise output JSON_EXTRACT(this, expression)
28593        if e.variant_extract.is_some() {
28594            use crate::dialects::DialectType;
28595            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28596                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28597                self.generate_expression(&e.this)?;
28598                self.write(":");
28599                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28600                // We need to output it without quotes
28601                match e.expression.as_ref() {
28602                    Expression::Literal(Literal::String(s)) => {
28603                        self.write(s);
28604                    }
28605                    _ => {
28606                        // Fallback: generate as-is (shouldn't happen in typical cases)
28607                        self.generate_expression(&e.expression)?;
28608                    }
28609                }
28610            } else {
28611                // Snowflake and others: use GET_PATH(col, 'path')
28612                self.write_keyword("GET_PATH");
28613                self.write("(");
28614                self.generate_expression(&e.this)?;
28615                self.write(", ");
28616                self.generate_expression(&e.expression)?;
28617                self.write(")");
28618            }
28619        } else {
28620            self.write_keyword("JSON_EXTRACT");
28621            self.write("(");
28622            self.generate_expression(&e.this)?;
28623            self.write(", ");
28624            self.generate_expression(&e.expression)?;
28625            for expr in &e.expressions {
28626                self.write(", ");
28627                self.generate_expression(expr)?;
28628            }
28629            self.write(")");
28630        }
28631        Ok(())
28632    }
28633
28634    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28635        // Output: {expr} FORMAT JSON
28636        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28637        if let Some(this) = &e.this {
28638            self.generate_expression(this)?;
28639            self.write_space();
28640        }
28641        self.write_keyword("FORMAT JSON");
28642        Ok(())
28643    }
28644
28645    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28646        // key: value (for JSON objects)
28647        self.generate_expression(&e.this)?;
28648        self.write(": ");
28649        self.generate_expression(&e.expression)?;
28650        Ok(())
28651    }
28652
28653    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28654        // JSON_KEYS(this, expression, expressions...)
28655        self.write_keyword("JSON_KEYS");
28656        self.write("(");
28657        self.generate_expression(&e.this)?;
28658        if let Some(expr) = &e.expression {
28659            self.write(", ");
28660            self.generate_expression(expr)?;
28661        }
28662        for expr in &e.expressions {
28663            self.write(", ");
28664            self.generate_expression(expr)?;
28665        }
28666        self.write(")");
28667        Ok(())
28668    }
28669
28670    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28671        // JSON_KEYS(this, expression)
28672        self.write_keyword("JSON_KEYS");
28673        self.write("(");
28674        self.generate_expression(&e.this)?;
28675        if let Some(expr) = &e.expression {
28676            self.write(", ");
28677            self.generate_expression(expr)?;
28678        }
28679        self.write(")");
28680        Ok(())
28681    }
28682
28683    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28684        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28685        // The path components are concatenated without spaces
28686        let mut path_str = String::new();
28687        for expr in &e.expressions {
28688            match expr {
28689                Expression::JSONPathRoot(_) => {
28690                    path_str.push('$');
28691                }
28692                Expression::JSONPathKey(k) => {
28693                    // .key or ."key" (quote if key has special characters)
28694                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28695                        k.this.as_ref()
28696                    {
28697                        path_str.push('.');
28698                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28699                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28700                        if needs_quoting {
28701                            path_str.push('"');
28702                            path_str.push_str(s);
28703                            path_str.push('"');
28704                        } else {
28705                            path_str.push_str(s);
28706                        }
28707                    }
28708                }
28709                Expression::JSONPathSubscript(s) => {
28710                    // [index]
28711                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28712                        s.this.as_ref()
28713                    {
28714                        path_str.push('[');
28715                        path_str.push_str(n);
28716                        path_str.push(']');
28717                    }
28718                }
28719                _ => {
28720                    // For other path parts, try to generate them
28721                    let mut temp_gen = Self::with_config(self.config.clone());
28722                    temp_gen.generate_expression(expr)?;
28723                    path_str.push_str(&temp_gen.output);
28724                }
28725            }
28726        }
28727        // Output as quoted string
28728        self.write("'");
28729        self.write(&path_str);
28730        self.write("'");
28731        Ok(())
28732    }
28733
28734    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28735        // JSON path filter: ?(predicate)
28736        self.write("?(");
28737        self.generate_expression(&e.this)?;
28738        self.write(")");
28739        Ok(())
28740    }
28741
28742    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28743        // JSON path key: .key or ["key"]
28744        self.write(".");
28745        self.generate_expression(&e.this)?;
28746        Ok(())
28747    }
28748
28749    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28750        // JSON path recursive descent: ..
28751        self.write("..");
28752        if let Some(this) = &e.this {
28753            self.generate_expression(this)?;
28754        }
28755        Ok(())
28756    }
28757
28758    fn generate_json_path_root(&mut self) -> Result<()> {
28759        // JSON path root: $
28760        self.write("$");
28761        Ok(())
28762    }
28763
28764    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28765        // JSON path script: (expression)
28766        self.write("(");
28767        self.generate_expression(&e.this)?;
28768        self.write(")");
28769        Ok(())
28770    }
28771
28772    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28773        // JSON path selector: *
28774        self.generate_expression(&e.this)?;
28775        Ok(())
28776    }
28777
28778    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28779        // JSON path slice: [start:end:step]
28780        self.write("[");
28781        if let Some(start) = &e.start {
28782            self.generate_expression(start)?;
28783        }
28784        self.write(":");
28785        if let Some(end) = &e.end {
28786            self.generate_expression(end)?;
28787        }
28788        if let Some(step) = &e.step {
28789            self.write(":");
28790            self.generate_expression(step)?;
28791        }
28792        self.write("]");
28793        Ok(())
28794    }
28795
28796    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28797        // JSON path subscript: [index] or [*]
28798        self.write("[");
28799        self.generate_expression(&e.this)?;
28800        self.write("]");
28801        Ok(())
28802    }
28803
28804    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28805        // JSON path union: [key1, key2, ...]
28806        self.write("[");
28807        for (i, expr) in e.expressions.iter().enumerate() {
28808            if i > 0 {
28809                self.write(", ");
28810            }
28811            self.generate_expression(expr)?;
28812        }
28813        self.write("]");
28814        Ok(())
28815    }
28816
28817    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28818        // JSON_REMOVE(this, path1, path2, ...)
28819        self.write_keyword("JSON_REMOVE");
28820        self.write("(");
28821        self.generate_expression(&e.this)?;
28822        for expr in &e.expressions {
28823            self.write(", ");
28824            self.generate_expression(expr)?;
28825        }
28826        self.write(")");
28827        Ok(())
28828    }
28829
28830    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28831        // COLUMNS(col1 type, col2 type, ...)
28832        // When pretty printing and content is too wide, format with each column on a separate line
28833        self.write_keyword("COLUMNS");
28834        self.write("(");
28835
28836        if self.config.pretty && !e.expressions.is_empty() {
28837            // First, generate all expressions into strings to check width
28838            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28839            for expr in &e.expressions {
28840                let mut temp_gen = Generator::with_config(self.config.clone());
28841                temp_gen.generate_expression(expr)?;
28842                expr_strings.push(temp_gen.output);
28843            }
28844
28845            // Check if total width exceeds max_text_width
28846            if self.too_wide(&expr_strings) {
28847                // Pretty print: each column on its own line
28848                self.write_newline();
28849                self.indent_level += 1;
28850                for (i, expr_str) in expr_strings.iter().enumerate() {
28851                    if i > 0 {
28852                        self.write(",");
28853                        self.write_newline();
28854                    }
28855                    self.write_indent();
28856                    self.write(expr_str);
28857                }
28858                self.write_newline();
28859                self.indent_level -= 1;
28860                self.write_indent();
28861            } else {
28862                // Compact: all on one line
28863                for (i, expr_str) in expr_strings.iter().enumerate() {
28864                    if i > 0 {
28865                        self.write(", ");
28866                    }
28867                    self.write(expr_str);
28868                }
28869            }
28870        } else {
28871            // Non-pretty mode: compact format
28872            for (i, expr) in e.expressions.iter().enumerate() {
28873                if i > 0 {
28874                    self.write(", ");
28875                }
28876                self.generate_expression(expr)?;
28877            }
28878        }
28879        self.write(")");
28880        Ok(())
28881    }
28882
28883    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28884        // JSON_SET(this, path, value, ...)
28885        self.write_keyword("JSON_SET");
28886        self.write("(");
28887        self.generate_expression(&e.this)?;
28888        for expr in &e.expressions {
28889            self.write(", ");
28890            self.generate_expression(expr)?;
28891        }
28892        self.write(")");
28893        Ok(())
28894    }
28895
28896    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28897        // JSON_STRIP_NULLS(this, expression)
28898        self.write_keyword("JSON_STRIP_NULLS");
28899        self.write("(");
28900        self.generate_expression(&e.this)?;
28901        if let Some(expr) = &e.expression {
28902            self.write(", ");
28903            self.generate_expression(expr)?;
28904        }
28905        self.write(")");
28906        Ok(())
28907    }
28908
28909    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28910        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28911        self.write_keyword("JSON_TABLE");
28912        self.write("(");
28913        self.generate_expression(&e.this)?;
28914        if let Some(path) = &e.path {
28915            self.write(", ");
28916            self.generate_expression(path)?;
28917        }
28918        if let Some(error_handling) = &e.error_handling {
28919            self.write_space();
28920            self.generate_expression(error_handling)?;
28921        }
28922        if let Some(empty_handling) = &e.empty_handling {
28923            self.write_space();
28924            self.generate_expression(empty_handling)?;
28925        }
28926        if let Some(schema) = &e.schema {
28927            self.write_space();
28928            self.generate_expression(schema)?;
28929        }
28930        self.write(")");
28931        Ok(())
28932    }
28933
28934    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28935        // JSON_TYPE(this)
28936        self.write_keyword("JSON_TYPE");
28937        self.write("(");
28938        self.generate_expression(&e.this)?;
28939        self.write(")");
28940        Ok(())
28941    }
28942
28943    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28944        // JSON_VALUE(this, path RETURNING type ON condition)
28945        self.write_keyword("JSON_VALUE");
28946        self.write("(");
28947        self.generate_expression(&e.this)?;
28948        if let Some(path) = &e.path {
28949            self.write(", ");
28950            self.generate_expression(path)?;
28951        }
28952        if let Some(returning) = &e.returning {
28953            self.write_space();
28954            self.write_keyword("RETURNING");
28955            self.write_space();
28956            self.generate_expression(returning)?;
28957        }
28958        if let Some(on_condition) = &e.on_condition {
28959            self.write_space();
28960            self.generate_expression(on_condition)?;
28961        }
28962        self.write(")");
28963        Ok(())
28964    }
28965
28966    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28967        // JSON_VALUE_ARRAY(this)
28968        self.write_keyword("JSON_VALUE_ARRAY");
28969        self.write("(");
28970        self.generate_expression(&e.this)?;
28971        self.write(")");
28972        Ok(())
28973    }
28974
28975    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28976        // JAROWINKLER_SIMILARITY(str1, str2)
28977        self.write_keyword("JAROWINKLER_SIMILARITY");
28978        self.write("(");
28979        self.generate_expression(&e.this)?;
28980        self.write(", ");
28981        self.generate_expression(&e.expression)?;
28982        self.write(")");
28983        Ok(())
28984    }
28985
28986    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28987        // Python: this(expressions)
28988        self.generate_expression(&e.this)?;
28989        self.write("(");
28990        for (i, expr) in e.expressions.iter().enumerate() {
28991            if i > 0 {
28992                self.write(", ");
28993            }
28994            self.generate_expression(expr)?;
28995        }
28996        self.write(")");
28997        Ok(())
28998    }
28999
29000    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
29001        // Python: {no}{local}{dual}{before}{after}JOURNAL
29002        if e.no.is_some() {
29003            self.write_keyword("NO ");
29004        }
29005        if let Some(local) = &e.local {
29006            self.generate_expression(local)?;
29007            self.write_space();
29008        }
29009        if e.dual.is_some() {
29010            self.write_keyword("DUAL ");
29011        }
29012        if e.before.is_some() {
29013            self.write_keyword("BEFORE ");
29014        }
29015        if e.after.is_some() {
29016            self.write_keyword("AFTER ");
29017        }
29018        self.write_keyword("JOURNAL");
29019        Ok(())
29020    }
29021
29022    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
29023        // LANGUAGE language_name
29024        self.write_keyword("LANGUAGE");
29025        self.write_space();
29026        self.generate_expression(&e.this)?;
29027        Ok(())
29028    }
29029
29030    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
29031        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
29032        if e.view.is_some() {
29033            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
29034            self.write_keyword("LATERAL VIEW");
29035            if e.outer.is_some() {
29036                self.write_space();
29037                self.write_keyword("OUTER");
29038            }
29039            self.write_space();
29040            self.generate_expression(&e.this)?;
29041            if let Some(alias) = &e.alias {
29042                self.write_space();
29043                self.write(alias);
29044            }
29045        } else {
29046            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
29047            self.write_keyword("LATERAL");
29048            self.write_space();
29049            self.generate_expression(&e.this)?;
29050            if e.ordinality.is_some() {
29051                self.write_space();
29052                self.write_keyword("WITH ORDINALITY");
29053            }
29054            if let Some(alias) = &e.alias {
29055                self.write_space();
29056                self.write_keyword("AS");
29057                self.write_space();
29058                self.write(alias);
29059                if !e.column_aliases.is_empty() {
29060                    self.write("(");
29061                    for (i, col) in e.column_aliases.iter().enumerate() {
29062                        if i > 0 {
29063                            self.write(", ");
29064                        }
29065                        self.write(col);
29066                    }
29067                    self.write(")");
29068                }
29069            }
29070        }
29071        Ok(())
29072    }
29073
29074    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29075        // Python: LIKE this [options]
29076        self.write_keyword("LIKE");
29077        self.write_space();
29078        self.generate_expression(&e.this)?;
29079        for expr in &e.expressions {
29080            self.write_space();
29081            self.generate_expression(expr)?;
29082        }
29083        Ok(())
29084    }
29085
29086    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29087        self.write_keyword("LIMIT");
29088        self.write_space();
29089        self.write_limit_expr(&e.this)?;
29090        if e.percent {
29091            self.write_space();
29092            self.write_keyword("PERCENT");
29093        }
29094        // Emit any comments that were captured from before the LIMIT keyword
29095        for comment in &e.comments {
29096            self.write(" ");
29097            self.write_formatted_comment(comment);
29098        }
29099        Ok(())
29100    }
29101
29102    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29103        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29104        if e.percent.is_some() {
29105            self.write_keyword(" PERCENT");
29106        }
29107        if e.rows.is_some() {
29108            self.write_keyword(" ROWS");
29109        }
29110        if e.with_ties.is_some() {
29111            self.write_keyword(" WITH TIES");
29112        } else if e.rows.is_some() {
29113            self.write_keyword(" ONLY");
29114        }
29115        Ok(())
29116    }
29117
29118    fn generate_list(&mut self, e: &List) -> Result<()> {
29119        use crate::dialects::DialectType;
29120        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29121
29122        // Check if this is a subquery-based list (LIST(SELECT ...))
29123        if e.expressions.len() == 1 {
29124            if let Expression::Select(_) = &e.expressions[0] {
29125                self.write_keyword("LIST");
29126                self.write("(");
29127                self.generate_expression(&e.expressions[0])?;
29128                self.write(")");
29129                return Ok(());
29130            }
29131        }
29132
29133        // For Materialize, output as LIST[expr, expr, ...]
29134        if is_materialize {
29135            self.write_keyword("LIST");
29136            self.write("[");
29137            for (i, expr) in e.expressions.iter().enumerate() {
29138                if i > 0 {
29139                    self.write(", ");
29140                }
29141                self.generate_expression(expr)?;
29142            }
29143            self.write("]");
29144        } else {
29145            // For other dialects, output as LIST(expr, expr, ...)
29146            self.write_keyword("LIST");
29147            self.write("(");
29148            for (i, expr) in e.expressions.iter().enumerate() {
29149                if i > 0 {
29150                    self.write(", ");
29151                }
29152                self.generate_expression(expr)?;
29153            }
29154            self.write(")");
29155        }
29156        Ok(())
29157    }
29158
29159    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29160        // Check if this is a subquery-based map (MAP(SELECT ...))
29161        if let Expression::Select(_) = &*e.this {
29162            self.write_keyword("MAP");
29163            self.write("(");
29164            self.generate_expression(&e.this)?;
29165            self.write(")");
29166            return Ok(());
29167        }
29168
29169        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29170
29171        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29172        self.write_keyword("MAP");
29173        if is_duckdb {
29174            self.write(" {");
29175        } else {
29176            self.write("[");
29177        }
29178        if let Expression::Struct(s) = &*e.this {
29179            for (i, (_, expr)) in s.fields.iter().enumerate() {
29180                if i > 0 {
29181                    self.write(", ");
29182                }
29183                if let Expression::PropertyEQ(op) = expr {
29184                    self.generate_expression(&op.left)?;
29185                    if is_duckdb {
29186                        self.write(": ");
29187                    } else {
29188                        self.write(" => ");
29189                    }
29190                    self.generate_expression(&op.right)?;
29191                } else {
29192                    self.generate_expression(expr)?;
29193                }
29194            }
29195        }
29196        if is_duckdb {
29197            self.write("}");
29198        } else {
29199            self.write("]");
29200        }
29201        Ok(())
29202    }
29203
29204    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29205        // Python: LOCALTIME or LOCALTIME(precision)
29206        self.write_keyword("LOCALTIME");
29207        if let Some(precision) = &e.this {
29208            self.write("(");
29209            self.generate_expression(precision)?;
29210            self.write(")");
29211        }
29212        Ok(())
29213    }
29214
29215    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29216        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29217        self.write_keyword("LOCALTIMESTAMP");
29218        if let Some(precision) = &e.this {
29219            self.write("(");
29220            self.generate_expression(precision)?;
29221            self.write(")");
29222        }
29223        Ok(())
29224    }
29225
29226    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29227        // LOCATION 'path'
29228        self.write_keyword("LOCATION");
29229        self.write_space();
29230        self.generate_expression(&e.this)?;
29231        Ok(())
29232    }
29233
29234    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29235        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29236        if e.update.is_some() {
29237            if e.key.is_some() {
29238                self.write_keyword("FOR NO KEY UPDATE");
29239            } else {
29240                self.write_keyword("FOR UPDATE");
29241            }
29242        } else {
29243            if e.key.is_some() {
29244                self.write_keyword("FOR KEY SHARE");
29245            } else {
29246                self.write_keyword("FOR SHARE");
29247            }
29248        }
29249        if !e.expressions.is_empty() {
29250            self.write_keyword(" OF ");
29251            for (i, expr) in e.expressions.iter().enumerate() {
29252                if i > 0 {
29253                    self.write(", ");
29254                }
29255                self.generate_expression(expr)?;
29256            }
29257        }
29258        // Handle wait option following Python sqlglot convention:
29259        // - Boolean(true) -> NOWAIT
29260        // - Boolean(false) -> SKIP LOCKED
29261        // - Literal (number) -> WAIT n
29262        if let Some(wait) = &e.wait {
29263            match wait.as_ref() {
29264                Expression::Boolean(b) => {
29265                    if b.value {
29266                        self.write_keyword(" NOWAIT");
29267                    } else {
29268                        self.write_keyword(" SKIP LOCKED");
29269                    }
29270                }
29271                _ => {
29272                    // It's a literal (number), output WAIT n
29273                    self.write_keyword(" WAIT ");
29274                    self.generate_expression(wait)?;
29275                }
29276            }
29277        }
29278        Ok(())
29279    }
29280
29281    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29282        // LOCK property
29283        self.write_keyword("LOCK");
29284        self.write_space();
29285        self.generate_expression(&e.this)?;
29286        Ok(())
29287    }
29288
29289    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29290        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29291        self.write_keyword("LOCKING");
29292        self.write_space();
29293        self.write(&e.kind);
29294        if let Some(this) = &e.this {
29295            self.write_space();
29296            self.generate_expression(this)?;
29297        }
29298        if let Some(for_or_in) = &e.for_or_in {
29299            self.write_space();
29300            self.generate_expression(for_or_in)?;
29301        }
29302        if let Some(lock_type) = &e.lock_type {
29303            self.write_space();
29304            self.generate_expression(lock_type)?;
29305        }
29306        if e.override_.is_some() {
29307            self.write_keyword(" OVERRIDE");
29308        }
29309        Ok(())
29310    }
29311
29312    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29313        // this expression
29314        self.generate_expression(&e.this)?;
29315        self.write_space();
29316        self.generate_expression(&e.expression)?;
29317        Ok(())
29318    }
29319
29320    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29321        // [NO] LOG
29322        if e.no.is_some() {
29323            self.write_keyword("NO ");
29324        }
29325        self.write_keyword("LOG");
29326        Ok(())
29327    }
29328
29329    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29330        // MD5(this, expressions...)
29331        self.write_keyword("MD5");
29332        self.write("(");
29333        self.generate_expression(&e.this)?;
29334        for expr in &e.expressions {
29335            self.write(", ");
29336            self.generate_expression(expr)?;
29337        }
29338        self.write(")");
29339        Ok(())
29340    }
29341
29342    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29343        // ML.FORECAST(model, [params])
29344        self.write_keyword("ML.FORECAST");
29345        self.write("(");
29346        self.generate_expression(&e.this)?;
29347        if let Some(expression) = &e.expression {
29348            self.write(", ");
29349            self.generate_expression(expression)?;
29350        }
29351        if let Some(params) = &e.params_struct {
29352            self.write(", ");
29353            self.generate_expression(params)?;
29354        }
29355        self.write(")");
29356        Ok(())
29357    }
29358
29359    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29360        // ML.TRANSLATE(model, input, [params])
29361        self.write_keyword("ML.TRANSLATE");
29362        self.write("(");
29363        self.generate_expression(&e.this)?;
29364        self.write(", ");
29365        self.generate_expression(&e.expression)?;
29366        if let Some(params) = &e.params_struct {
29367            self.write(", ");
29368            self.generate_expression(params)?;
29369        }
29370        self.write(")");
29371        Ok(())
29372    }
29373
29374    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29375        // MAKE_INTERVAL(years => x, months => y, ...)
29376        self.write_keyword("MAKE_INTERVAL");
29377        self.write("(");
29378        let mut first = true;
29379        if let Some(year) = &e.year {
29380            self.write("years => ");
29381            self.generate_expression(year)?;
29382            first = false;
29383        }
29384        if let Some(month) = &e.month {
29385            if !first {
29386                self.write(", ");
29387            }
29388            self.write("months => ");
29389            self.generate_expression(month)?;
29390            first = false;
29391        }
29392        if let Some(week) = &e.week {
29393            if !first {
29394                self.write(", ");
29395            }
29396            self.write("weeks => ");
29397            self.generate_expression(week)?;
29398            first = false;
29399        }
29400        if let Some(day) = &e.day {
29401            if !first {
29402                self.write(", ");
29403            }
29404            self.write("days => ");
29405            self.generate_expression(day)?;
29406            first = false;
29407        }
29408        if let Some(hour) = &e.hour {
29409            if !first {
29410                self.write(", ");
29411            }
29412            self.write("hours => ");
29413            self.generate_expression(hour)?;
29414            first = false;
29415        }
29416        if let Some(minute) = &e.minute {
29417            if !first {
29418                self.write(", ");
29419            }
29420            self.write("mins => ");
29421            self.generate_expression(minute)?;
29422            first = false;
29423        }
29424        if let Some(second) = &e.second {
29425            if !first {
29426                self.write(", ");
29427            }
29428            self.write("secs => ");
29429            self.generate_expression(second)?;
29430        }
29431        self.write(")");
29432        Ok(())
29433    }
29434
29435    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29436        // MANHATTAN_DISTANCE(vector1, vector2)
29437        self.write_keyword("MANHATTAN_DISTANCE");
29438        self.write("(");
29439        self.generate_expression(&e.this)?;
29440        self.write(", ");
29441        self.generate_expression(&e.expression)?;
29442        self.write(")");
29443        Ok(())
29444    }
29445
29446    fn generate_map(&mut self, e: &Map) -> Result<()> {
29447        // MAP(key1, value1, key2, value2, ...)
29448        self.write_keyword("MAP");
29449        self.write("(");
29450        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29451            if i > 0 {
29452                self.write(", ");
29453            }
29454            self.generate_expression(key)?;
29455            self.write(", ");
29456            self.generate_expression(value)?;
29457        }
29458        self.write(")");
29459        Ok(())
29460    }
29461
29462    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29463        // MAP_CAT(map1, map2)
29464        self.write_keyword("MAP_CAT");
29465        self.write("(");
29466        self.generate_expression(&e.this)?;
29467        self.write(", ");
29468        self.generate_expression(&e.expression)?;
29469        self.write(")");
29470        Ok(())
29471    }
29472
29473    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29474        // MAP_DELETE(map, key1, key2, ...)
29475        self.write_keyword("MAP_DELETE");
29476        self.write("(");
29477        self.generate_expression(&e.this)?;
29478        for expr in &e.expressions {
29479            self.write(", ");
29480            self.generate_expression(expr)?;
29481        }
29482        self.write(")");
29483        Ok(())
29484    }
29485
29486    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29487        // MAP_INSERT(map, key, value, [update_flag])
29488        self.write_keyword("MAP_INSERT");
29489        self.write("(");
29490        self.generate_expression(&e.this)?;
29491        if let Some(key) = &e.key {
29492            self.write(", ");
29493            self.generate_expression(key)?;
29494        }
29495        if let Some(value) = &e.value {
29496            self.write(", ");
29497            self.generate_expression(value)?;
29498        }
29499        if let Some(update_flag) = &e.update_flag {
29500            self.write(", ");
29501            self.generate_expression(update_flag)?;
29502        }
29503        self.write(")");
29504        Ok(())
29505    }
29506
29507    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29508        // MAP_PICK(map, key1, key2, ...)
29509        self.write_keyword("MAP_PICK");
29510        self.write("(");
29511        self.generate_expression(&e.this)?;
29512        for expr in &e.expressions {
29513            self.write(", ");
29514            self.generate_expression(expr)?;
29515        }
29516        self.write(")");
29517        Ok(())
29518    }
29519
29520    fn generate_masking_policy_column_constraint(
29521        &mut self,
29522        e: &MaskingPolicyColumnConstraint,
29523    ) -> Result<()> {
29524        // Python: MASKING POLICY name [USING (cols)]
29525        self.write_keyword("MASKING POLICY");
29526        self.write_space();
29527        self.generate_expression(&e.this)?;
29528        if !e.expressions.is_empty() {
29529            self.write_keyword(" USING");
29530            self.write(" (");
29531            for (i, expr) in e.expressions.iter().enumerate() {
29532                if i > 0 {
29533                    self.write(", ");
29534                }
29535                self.generate_expression(expr)?;
29536            }
29537            self.write(")");
29538        }
29539        Ok(())
29540    }
29541
29542    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29543        if matches!(
29544            self.config.dialect,
29545            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29546        ) {
29547            if e.expressions.len() > 1 {
29548                self.write("(");
29549            }
29550            for (i, expr) in e.expressions.iter().enumerate() {
29551                if i > 0 {
29552                    self.write_keyword(" OR ");
29553                }
29554                self.generate_expression(expr)?;
29555                self.write_space();
29556                self.write("@@");
29557                self.write_space();
29558                self.generate_expression(&e.this)?;
29559            }
29560            if e.expressions.len() > 1 {
29561                self.write(")");
29562            }
29563            return Ok(());
29564        }
29565
29566        // MATCH(columns) AGAINST(expr [modifier])
29567        self.write_keyword("MATCH");
29568        self.write("(");
29569        for (i, expr) in e.expressions.iter().enumerate() {
29570            if i > 0 {
29571                self.write(", ");
29572            }
29573            self.generate_expression(expr)?;
29574        }
29575        self.write(")");
29576        self.write_keyword(" AGAINST");
29577        self.write("(");
29578        self.generate_expression(&e.this)?;
29579        if let Some(modifier) = &e.modifier {
29580            self.write_space();
29581            self.generate_expression(modifier)?;
29582        }
29583        self.write(")");
29584        Ok(())
29585    }
29586
29587    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29588        // Python: [window_frame] this
29589        if let Some(window_frame) = &e.window_frame {
29590            self.write(&format!("{:?}", window_frame).to_uppercase());
29591            self.write_space();
29592        }
29593        self.generate_expression(&e.this)?;
29594        Ok(())
29595    }
29596
29597    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29598        // MATERIALIZED [this]
29599        self.write_keyword("MATERIALIZED");
29600        if let Some(this) = &e.this {
29601            self.write_space();
29602            self.generate_expression(this)?;
29603        }
29604        Ok(())
29605    }
29606
29607    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29608        // MERGE INTO target USING source ON condition WHEN ...
29609        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29610        if let Some(with_) = &e.with_ {
29611            self.generate_expression(with_)?;
29612            self.write_space();
29613        }
29614        self.write_keyword("MERGE INTO");
29615        self.write_space();
29616        self.generate_expression(&e.this)?;
29617
29618        // USING clause - newline before in pretty mode
29619        if self.config.pretty {
29620            self.write_newline();
29621            self.write_indent();
29622        } else {
29623            self.write_space();
29624        }
29625        self.write_keyword("USING");
29626        self.write_space();
29627        self.generate_expression(&e.using)?;
29628
29629        // ON clause - newline before in pretty mode
29630        if let Some(on) = &e.on {
29631            if self.config.pretty {
29632                self.write_newline();
29633                self.write_indent();
29634            } else {
29635                self.write_space();
29636            }
29637            self.write_keyword("ON");
29638            self.write_space();
29639            self.generate_expression(on)?;
29640        }
29641        // DuckDB USING (key_columns) clause
29642        if let Some(using_cond) = &e.using_cond {
29643            self.write_space();
29644            self.write_keyword("USING");
29645            self.write_space();
29646            self.write("(");
29647            // using_cond is a Tuple containing the column identifiers
29648            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29649                for (i, col) in tuple.expressions.iter().enumerate() {
29650                    if i > 0 {
29651                        self.write(", ");
29652                    }
29653                    self.generate_expression(col)?;
29654                }
29655            } else {
29656                self.generate_expression(using_cond)?;
29657            }
29658            self.write(")");
29659        }
29660        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29661        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29662        if matches!(
29663            self.config.dialect,
29664            Some(crate::DialectType::PostgreSQL)
29665                | Some(crate::DialectType::Redshift)
29666                | Some(crate::DialectType::Trino)
29667                | Some(crate::DialectType::Presto)
29668                | Some(crate::DialectType::Athena)
29669        ) {
29670            let mut names = Vec::new();
29671            match e.this.as_ref() {
29672                Expression::Alias(a) => {
29673                    // e.g., "x AS z" -> strip both "x" and "z"
29674                    if let Expression::Table(t) = &a.this {
29675                        names.push(t.name.name.clone());
29676                    } else if let Expression::Identifier(id) = &a.this {
29677                        names.push(id.name.clone());
29678                    }
29679                    names.push(a.alias.name.clone());
29680                }
29681                Expression::Table(t) => {
29682                    names.push(t.name.name.clone());
29683                }
29684                Expression::Identifier(id) => {
29685                    names.push(id.name.clone());
29686                }
29687                _ => {}
29688            }
29689            self.merge_strip_qualifiers = names;
29690        }
29691
29692        // WHEN clauses - newline before each in pretty mode
29693        if let Some(whens) = &e.whens {
29694            if self.config.pretty {
29695                self.write_newline();
29696                self.write_indent();
29697            } else {
29698                self.write_space();
29699            }
29700            self.generate_expression(whens)?;
29701        }
29702
29703        // Restore merge_strip_qualifiers
29704        self.merge_strip_qualifiers = saved_merge_strip;
29705
29706        // OUTPUT/RETURNING clause - newline before in pretty mode
29707        if let Some(returning) = &e.returning {
29708            if self.config.pretty {
29709                self.write_newline();
29710                self.write_indent();
29711            } else {
29712                self.write_space();
29713            }
29714            self.generate_expression(returning)?;
29715        }
29716        Ok(())
29717    }
29718
29719    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29720        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29721        if e.no.is_some() {
29722            self.write_keyword("NO MERGEBLOCKRATIO");
29723        } else if e.default.is_some() {
29724            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29725        } else {
29726            self.write_keyword("MERGEBLOCKRATIO");
29727            self.write("=");
29728            if let Some(this) = &e.this {
29729                self.generate_expression(this)?;
29730            }
29731            if e.percent.is_some() {
29732                self.write_keyword(" PERCENT");
29733            }
29734        }
29735        Ok(())
29736    }
29737
29738    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29739        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29740        self.write_keyword("TTL");
29741        let pretty_clickhouse = self.config.pretty
29742            && matches!(
29743                self.config.dialect,
29744                Some(crate::dialects::DialectType::ClickHouse)
29745            );
29746
29747        if pretty_clickhouse {
29748            self.write_newline();
29749            self.indent_level += 1;
29750            for (i, expr) in e.expressions.iter().enumerate() {
29751                if i > 0 {
29752                    self.write(",");
29753                    self.write_newline();
29754                }
29755                self.write_indent();
29756                self.generate_expression(expr)?;
29757            }
29758            self.indent_level -= 1;
29759        } else {
29760            self.write_space();
29761            for (i, expr) in e.expressions.iter().enumerate() {
29762                if i > 0 {
29763                    self.write(", ");
29764                }
29765                self.generate_expression(expr)?;
29766            }
29767        }
29768
29769        if let Some(where_) = &e.where_ {
29770            if pretty_clickhouse {
29771                self.write_newline();
29772                if let Expression::Where(w) = where_.as_ref() {
29773                    self.write_indent();
29774                    self.write_keyword("WHERE");
29775                    self.write_newline();
29776                    self.indent_level += 1;
29777                    self.write_indent();
29778                    self.generate_expression(&w.this)?;
29779                    self.indent_level -= 1;
29780                } else {
29781                    self.write_indent();
29782                    self.generate_expression(where_)?;
29783                }
29784            } else {
29785                self.write_space();
29786                self.generate_expression(where_)?;
29787            }
29788        }
29789        if let Some(group) = &e.group {
29790            if pretty_clickhouse {
29791                self.write_newline();
29792                if let Expression::Group(g) = group.as_ref() {
29793                    self.write_indent();
29794                    self.write_keyword("GROUP BY");
29795                    self.write_newline();
29796                    self.indent_level += 1;
29797                    for (i, expr) in g.expressions.iter().enumerate() {
29798                        if i > 0 {
29799                            self.write(",");
29800                            self.write_newline();
29801                        }
29802                        self.write_indent();
29803                        self.generate_expression(expr)?;
29804                    }
29805                    self.indent_level -= 1;
29806                } else {
29807                    self.write_indent();
29808                    self.generate_expression(group)?;
29809                }
29810            } else {
29811                self.write_space();
29812                self.generate_expression(group)?;
29813            }
29814        }
29815        if let Some(aggregates) = &e.aggregates {
29816            if pretty_clickhouse {
29817                self.write_newline();
29818                self.write_indent();
29819                self.write_keyword("SET");
29820                self.write_newline();
29821                self.indent_level += 1;
29822                if let Expression::Tuple(t) = aggregates.as_ref() {
29823                    for (i, agg) in t.expressions.iter().enumerate() {
29824                        if i > 0 {
29825                            self.write(",");
29826                            self.write_newline();
29827                        }
29828                        self.write_indent();
29829                        self.generate_expression(agg)?;
29830                    }
29831                } else {
29832                    self.write_indent();
29833                    self.generate_expression(aggregates)?;
29834                }
29835                self.indent_level -= 1;
29836            } else {
29837                self.write_space();
29838                self.write_keyword("SET");
29839                self.write_space();
29840                self.generate_expression(aggregates)?;
29841            }
29842        }
29843        Ok(())
29844    }
29845
29846    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29847        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29848        self.generate_expression(&e.this)?;
29849        if e.delete.is_some() {
29850            self.write_keyword(" DELETE");
29851        }
29852        if let Some(recompress) = &e.recompress {
29853            self.write_keyword(" RECOMPRESS ");
29854            self.generate_expression(recompress)?;
29855        }
29856        if let Some(to_disk) = &e.to_disk {
29857            self.write_keyword(" TO DISK ");
29858            self.generate_expression(to_disk)?;
29859        }
29860        if let Some(to_volume) = &e.to_volume {
29861            self.write_keyword(" TO VOLUME ");
29862            self.generate_expression(to_volume)?;
29863        }
29864        Ok(())
29865    }
29866
29867    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29868        // MINHASH(this, expressions...)
29869        self.write_keyword("MINHASH");
29870        self.write("(");
29871        self.generate_expression(&e.this)?;
29872        for expr in &e.expressions {
29873            self.write(", ");
29874            self.generate_expression(expr)?;
29875        }
29876        self.write(")");
29877        Ok(())
29878    }
29879
29880    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29881        // model!attribute - Snowflake syntax
29882        self.generate_expression(&e.this)?;
29883        self.write("!");
29884        self.generate_expression(&e.expression)?;
29885        Ok(())
29886    }
29887
29888    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29889        // MONTHNAME(this)
29890        self.write_keyword("MONTHNAME");
29891        self.write("(");
29892        self.generate_expression(&e.this)?;
29893        self.write(")");
29894        Ok(())
29895    }
29896
29897    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29898        // Output leading comments
29899        for comment in &e.leading_comments {
29900            self.write_formatted_comment(comment);
29901            if self.config.pretty {
29902                self.write_newline();
29903                self.write_indent();
29904            } else {
29905                self.write_space();
29906            }
29907        }
29908        // Python: INSERT kind expressions source
29909        self.write_keyword("INSERT");
29910        self.write_space();
29911        self.write(&e.kind);
29912        if self.config.pretty {
29913            self.indent_level += 1;
29914            for expr in &e.expressions {
29915                self.write_newline();
29916                self.write_indent();
29917                self.generate_expression(expr)?;
29918            }
29919            self.indent_level -= 1;
29920        } else {
29921            for expr in &e.expressions {
29922                self.write_space();
29923                self.generate_expression(expr)?;
29924            }
29925        }
29926        if let Some(source) = &e.source {
29927            if self.config.pretty {
29928                self.write_newline();
29929                self.write_indent();
29930            } else {
29931                self.write_space();
29932            }
29933            self.generate_expression(source)?;
29934        }
29935        Ok(())
29936    }
29937
29938    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29939        // Python: NEXT VALUE FOR this [OVER (order)]
29940        self.write_keyword("NEXT VALUE FOR");
29941        self.write_space();
29942        self.generate_expression(&e.this)?;
29943        if let Some(order) = &e.order {
29944            self.write_space();
29945            self.write_keyword("OVER");
29946            self.write(" (");
29947            self.generate_expression(order)?;
29948            self.write(")");
29949        }
29950        Ok(())
29951    }
29952
29953    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29954        // NORMAL(mean, stddev, gen)
29955        self.write_keyword("NORMAL");
29956        self.write("(");
29957        self.generate_expression(&e.this)?;
29958        if let Some(stddev) = &e.stddev {
29959            self.write(", ");
29960            self.generate_expression(stddev)?;
29961        }
29962        if let Some(gen) = &e.gen {
29963            self.write(", ");
29964            self.generate_expression(gen)?;
29965        }
29966        self.write(")");
29967        Ok(())
29968    }
29969
29970    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29971        // NORMALIZE(this, form) or CASEFOLD version
29972        if e.is_casefold.is_some() {
29973            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29974        } else {
29975            self.write_keyword("NORMALIZE");
29976        }
29977        self.write("(");
29978        self.generate_expression(&e.this)?;
29979        if let Some(form) = &e.form {
29980            self.write(", ");
29981            self.generate_expression(form)?;
29982        }
29983        self.write(")");
29984        Ok(())
29985    }
29986
29987    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29988        // Python: [NOT ]NULL
29989        if e.allow_null.is_none() {
29990            self.write_keyword("NOT ");
29991        }
29992        self.write_keyword("NULL");
29993        Ok(())
29994    }
29995
29996    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
29997        // NULLIF(this, expression)
29998        self.write_keyword("NULLIF");
29999        self.write("(");
30000        self.generate_expression(&e.this)?;
30001        self.write(", ");
30002        self.generate_expression(&e.expression)?;
30003        self.write(")");
30004        Ok(())
30005    }
30006
30007    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
30008        // FORMAT(this, format, culture)
30009        self.write_keyword("FORMAT");
30010        self.write("(");
30011        self.generate_expression(&e.this)?;
30012        self.write(", '");
30013        self.write(&e.format);
30014        self.write("'");
30015        if let Some(culture) = &e.culture {
30016            self.write(", ");
30017            self.generate_expression(culture)?;
30018        }
30019        self.write(")");
30020        Ok(())
30021    }
30022
30023    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
30024        // OBJECT_AGG(key, value)
30025        self.write_keyword("OBJECT_AGG");
30026        self.write("(");
30027        self.generate_expression(&e.this)?;
30028        self.write(", ");
30029        self.generate_expression(&e.expression)?;
30030        self.write(")");
30031        Ok(())
30032    }
30033
30034    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
30035        // Python: Just returns the name
30036        self.generate_expression(&e.this)?;
30037        Ok(())
30038    }
30039
30040    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
30041        // OBJECT_INSERT(obj, key, value, [update_flag])
30042        self.write_keyword("OBJECT_INSERT");
30043        self.write("(");
30044        self.generate_expression(&e.this)?;
30045        if let Some(key) = &e.key {
30046            self.write(", ");
30047            self.generate_expression(key)?;
30048        }
30049        if let Some(value) = &e.value {
30050            self.write(", ");
30051            self.generate_expression(value)?;
30052        }
30053        if let Some(update_flag) = &e.update_flag {
30054            self.write(", ");
30055            self.generate_expression(update_flag)?;
30056        }
30057        self.write(")");
30058        Ok(())
30059    }
30060
30061    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
30062        // OFFSET value [ROW|ROWS]
30063        self.write_keyword("OFFSET");
30064        self.write_space();
30065        self.generate_expression(&e.this)?;
30066        // Output ROWS keyword only for TSQL/Oracle targets
30067        if e.rows == Some(true)
30068            && matches!(
30069                self.config.dialect,
30070                Some(crate::dialects::DialectType::TSQL)
30071                    | Some(crate::dialects::DialectType::Oracle)
30072            )
30073        {
30074            self.write_space();
30075            self.write_keyword("ROWS");
30076        }
30077        Ok(())
30078    }
30079
30080    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30081        // QUALIFY condition (Snowflake/BigQuery)
30082        self.write_keyword("QUALIFY");
30083        self.write_space();
30084        self.generate_expression(&e.this)?;
30085        Ok(())
30086    }
30087
30088    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30089        // ON CLUSTER cluster_name
30090        self.write_keyword("ON CLUSTER");
30091        self.write_space();
30092        self.generate_expression(&e.this)?;
30093        Ok(())
30094    }
30095
30096    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30097        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30098        self.write_keyword("ON COMMIT");
30099        if e.delete.is_some() {
30100            self.write_keyword(" DELETE ROWS");
30101        } else {
30102            self.write_keyword(" PRESERVE ROWS");
30103        }
30104        Ok(())
30105    }
30106
30107    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30108        // Python: error/empty/null handling
30109        if let Some(empty) = &e.empty {
30110            self.generate_expression(empty)?;
30111            self.write_keyword(" ON EMPTY");
30112        }
30113        if let Some(error) = &e.error {
30114            if e.empty.is_some() {
30115                self.write_space();
30116            }
30117            self.generate_expression(error)?;
30118            self.write_keyword(" ON ERROR");
30119        }
30120        if let Some(null) = &e.null {
30121            if e.empty.is_some() || e.error.is_some() {
30122                self.write_space();
30123            }
30124            self.generate_expression(null)?;
30125            self.write_keyword(" ON NULL");
30126        }
30127        Ok(())
30128    }
30129
30130    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30131        // Materialize doesn't support ON CONFLICT - skip entirely
30132        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30133            return Ok(());
30134        }
30135        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30136        if e.duplicate.is_some() {
30137            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30138            self.write_keyword("ON DUPLICATE KEY UPDATE");
30139            for (i, expr) in e.expressions.iter().enumerate() {
30140                if i > 0 {
30141                    self.write(",");
30142                }
30143                self.write_space();
30144                self.generate_expression(expr)?;
30145            }
30146            return Ok(());
30147        } else {
30148            self.write_keyword("ON CONFLICT");
30149        }
30150        if let Some(constraint) = &e.constraint {
30151            self.write_keyword(" ON CONSTRAINT ");
30152            self.generate_expression(constraint)?;
30153        }
30154        if let Some(conflict_keys) = &e.conflict_keys {
30155            // conflict_keys can be a Tuple containing expressions
30156            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30157                self.write("(");
30158                for (i, expr) in t.expressions.iter().enumerate() {
30159                    if i > 0 {
30160                        self.write(", ");
30161                    }
30162                    self.generate_expression(expr)?;
30163                }
30164                self.write(")");
30165            } else {
30166                self.write("(");
30167                self.generate_expression(conflict_keys)?;
30168                self.write(")");
30169            }
30170        }
30171        if let Some(index_predicate) = &e.index_predicate {
30172            self.write_keyword(" WHERE ");
30173            self.generate_expression(index_predicate)?;
30174        }
30175        if let Some(action) = &e.action {
30176            // Check if action is "NOTHING" or an UPDATE set
30177            if let Expression::Identifier(id) = action.as_ref() {
30178                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30179                    self.write_keyword(" DO NOTHING");
30180                } else {
30181                    self.write_keyword(" DO ");
30182                    self.generate_expression(action)?;
30183                }
30184            } else if let Expression::Tuple(t) = action.as_ref() {
30185                // DO UPDATE SET col1 = val1, col2 = val2
30186                self.write_keyword(" DO UPDATE SET ");
30187                for (i, expr) in t.expressions.iter().enumerate() {
30188                    if i > 0 {
30189                        self.write(", ");
30190                    }
30191                    self.generate_expression(expr)?;
30192                }
30193            } else {
30194                self.write_keyword(" DO ");
30195                self.generate_expression(action)?;
30196            }
30197        }
30198        // WHERE clause for the UPDATE action
30199        if let Some(where_) = &e.where_ {
30200            self.write_keyword(" WHERE ");
30201            self.generate_expression(where_)?;
30202        }
30203        Ok(())
30204    }
30205
30206    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30207        // ON property_value
30208        self.write_keyword("ON");
30209        self.write_space();
30210        self.generate_expression(&e.this)?;
30211        Ok(())
30212    }
30213
30214    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30215        // Python: this expression (e.g., column opclass)
30216        self.generate_expression(&e.this)?;
30217        self.write_space();
30218        self.generate_expression(&e.expression)?;
30219        Ok(())
30220    }
30221
30222    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30223        // Python: OPENJSON(this[, path]) [WITH (columns)]
30224        self.write_keyword("OPENJSON");
30225        self.write("(");
30226        self.generate_expression(&e.this)?;
30227        if let Some(path) = &e.path {
30228            self.write(", ");
30229            self.generate_expression(path)?;
30230        }
30231        self.write(")");
30232        if !e.expressions.is_empty() {
30233            self.write_keyword(" WITH");
30234            if self.config.pretty {
30235                self.write(" (\n");
30236                self.indent_level += 2;
30237                for (i, expr) in e.expressions.iter().enumerate() {
30238                    if i > 0 {
30239                        self.write(",\n");
30240                    }
30241                    self.write_indent();
30242                    self.generate_expression(expr)?;
30243                }
30244                self.write("\n");
30245                self.indent_level -= 2;
30246                self.write(")");
30247            } else {
30248                self.write(" (");
30249                for (i, expr) in e.expressions.iter().enumerate() {
30250                    if i > 0 {
30251                        self.write(", ");
30252                    }
30253                    self.generate_expression(expr)?;
30254                }
30255                self.write(")");
30256            }
30257        }
30258        Ok(())
30259    }
30260
30261    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30262        // Python: this kind [path] [AS JSON]
30263        self.generate_expression(&e.this)?;
30264        self.write_space();
30265        // Use parsed data_type if available, otherwise fall back to kind string
30266        if let Some(ref dt) = e.data_type {
30267            self.generate_data_type(dt)?;
30268        } else if !e.kind.is_empty() {
30269            self.write(&e.kind);
30270        }
30271        if let Some(path) = &e.path {
30272            self.write_space();
30273            self.generate_expression(path)?;
30274        }
30275        if e.as_json.is_some() {
30276            self.write_keyword(" AS JSON");
30277        }
30278        Ok(())
30279    }
30280
30281    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30282        // this OPERATOR(op) expression
30283        self.generate_expression(&e.this)?;
30284        self.write_space();
30285        if let Some(op) = &e.operator {
30286            self.write_keyword("OPERATOR");
30287            self.write("(");
30288            self.generate_expression(op)?;
30289            self.write(")");
30290        }
30291        // Emit inline comments between OPERATOR() and the RHS
30292        for comment in &e.comments {
30293            self.write_space();
30294            self.write_formatted_comment(comment);
30295        }
30296        self.write_space();
30297        self.generate_expression(&e.expression)?;
30298        Ok(())
30299    }
30300
30301    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30302        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30303        self.write_keyword("ORDER BY");
30304        let pretty_clickhouse_single_paren = self.config.pretty
30305            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30306            && e.expressions.len() == 1
30307            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30308        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30309            && e.expressions.len() == 1
30310            && matches!(e.expressions[0].this, Expression::Tuple(_))
30311            && !e.expressions[0].desc
30312            && e.expressions[0].nulls_first.is_none();
30313
30314        if pretty_clickhouse_single_paren {
30315            self.write_space();
30316            if let Expression::Paren(p) = &e.expressions[0].this {
30317                self.write("(");
30318                self.write_newline();
30319                self.indent_level += 1;
30320                self.write_indent();
30321                self.generate_expression(&p.this)?;
30322                self.indent_level -= 1;
30323                self.write_newline();
30324                self.write(")");
30325            }
30326            return Ok(());
30327        }
30328
30329        if clickhouse_single_tuple {
30330            self.write_space();
30331            if let Expression::Tuple(t) = &e.expressions[0].this {
30332                self.write("(");
30333                for (i, expr) in t.expressions.iter().enumerate() {
30334                    if i > 0 {
30335                        self.write(", ");
30336                    }
30337                    self.generate_expression(expr)?;
30338                }
30339                self.write(")");
30340            }
30341            return Ok(());
30342        }
30343
30344        self.write_space();
30345        for (i, ordered) in e.expressions.iter().enumerate() {
30346            if i > 0 {
30347                self.write(", ");
30348            }
30349            self.generate_expression(&ordered.this)?;
30350            if ordered.desc {
30351                self.write_space();
30352                self.write_keyword("DESC");
30353            } else if ordered.explicit_asc {
30354                self.write_space();
30355                self.write_keyword("ASC");
30356            }
30357            if let Some(nulls_first) = ordered.nulls_first {
30358                // In Dremio, NULLS LAST is the default, so skip generating it
30359                let skip_nulls_last =
30360                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30361                if !skip_nulls_last {
30362                    self.write_space();
30363                    self.write_keyword("NULLS");
30364                    self.write_space();
30365                    if nulls_first {
30366                        self.write_keyword("FIRST");
30367                    } else {
30368                        self.write_keyword("LAST");
30369                    }
30370                }
30371            }
30372        }
30373        Ok(())
30374    }
30375
30376    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30377        // OUTPUT(model)
30378        self.write_keyword("OUTPUT");
30379        self.write("(");
30380        if self.config.pretty {
30381            self.indent_level += 1;
30382            self.write_newline();
30383            self.write_indent();
30384            self.generate_expression(&e.this)?;
30385            self.indent_level -= 1;
30386            self.write_newline();
30387        } else {
30388            self.generate_expression(&e.this)?;
30389        }
30390        self.write(")");
30391        Ok(())
30392    }
30393
30394    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30395        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30396        self.write_keyword("TRUNCATE");
30397        if let Some(this) = &e.this {
30398            self.write_space();
30399            self.generate_expression(this)?;
30400        }
30401        if e.with_count.is_some() {
30402            self.write_keyword(" WITH COUNT");
30403        } else {
30404            self.write_keyword(" WITHOUT COUNT");
30405        }
30406        Ok(())
30407    }
30408
30409    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30410        // Python: name(expressions)(params)
30411        self.generate_expression(&e.this)?;
30412        self.write("(");
30413        for (i, expr) in e.expressions.iter().enumerate() {
30414            if i > 0 {
30415                self.write(", ");
30416            }
30417            self.generate_expression(expr)?;
30418        }
30419        self.write(")(");
30420        for (i, param) in e.params.iter().enumerate() {
30421            if i > 0 {
30422                self.write(", ");
30423            }
30424            self.generate_expression(param)?;
30425        }
30426        self.write(")");
30427        Ok(())
30428    }
30429
30430    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30431        // PARSE_DATETIME(format, this) or similar
30432        self.write_keyword("PARSE_DATETIME");
30433        self.write("(");
30434        if let Some(format) = &e.format {
30435            self.write("'");
30436            self.write(format);
30437            self.write("', ");
30438        }
30439        self.generate_expression(&e.this)?;
30440        if let Some(zone) = &e.zone {
30441            self.write(", ");
30442            self.generate_expression(zone)?;
30443        }
30444        self.write(")");
30445        Ok(())
30446    }
30447
30448    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30449        // PARSE_IP(this, type, permissive)
30450        self.write_keyword("PARSE_IP");
30451        self.write("(");
30452        self.generate_expression(&e.this)?;
30453        if let Some(type_) = &e.type_ {
30454            self.write(", ");
30455            self.generate_expression(type_)?;
30456        }
30457        if let Some(permissive) = &e.permissive {
30458            self.write(", ");
30459            self.generate_expression(permissive)?;
30460        }
30461        self.write(")");
30462        Ok(())
30463    }
30464
30465    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30466        // PARSE_JSON(this, [expression])
30467        self.write_keyword("PARSE_JSON");
30468        self.write("(");
30469        self.generate_expression(&e.this)?;
30470        if let Some(expression) = &e.expression {
30471            self.write(", ");
30472            self.generate_expression(expression)?;
30473        }
30474        self.write(")");
30475        Ok(())
30476    }
30477
30478    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30479        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30480        self.write_keyword("PARSE_TIME");
30481        self.write("(");
30482        self.write(&format!("'{}'", e.format));
30483        self.write(", ");
30484        self.generate_expression(&e.this)?;
30485        self.write(")");
30486        Ok(())
30487    }
30488
30489    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30490        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30491        self.write_keyword("PARSE_URL");
30492        self.write("(");
30493        self.generate_expression(&e.this)?;
30494        if let Some(part) = &e.part_to_extract {
30495            self.write(", ");
30496            self.generate_expression(part)?;
30497        }
30498        if let Some(key) = &e.key {
30499            self.write(", ");
30500            self.generate_expression(key)?;
30501        }
30502        if let Some(permissive) = &e.permissive {
30503            self.write(", ");
30504            self.generate_expression(permissive)?;
30505        }
30506        self.write(")");
30507        Ok(())
30508    }
30509
30510    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30511        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30512        if e.subpartition {
30513            self.write_keyword("SUBPARTITION");
30514        } else {
30515            self.write_keyword("PARTITION");
30516        }
30517        self.write("(");
30518        for (i, expr) in e.expressions.iter().enumerate() {
30519            if i > 0 {
30520                self.write(", ");
30521            }
30522            self.generate_expression(expr)?;
30523        }
30524        self.write(")");
30525        Ok(())
30526    }
30527
30528    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30529        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30530        if let Some(this) = &e.this {
30531            if let Some(expression) = &e.expression {
30532                // WITH (MODULUS this, REMAINDER expression)
30533                self.write_keyword("WITH");
30534                self.write(" (");
30535                self.write_keyword("MODULUS");
30536                self.write_space();
30537                self.generate_expression(this)?;
30538                self.write(", ");
30539                self.write_keyword("REMAINDER");
30540                self.write_space();
30541                self.generate_expression(expression)?;
30542                self.write(")");
30543            } else {
30544                // IN (this) - this could be a list
30545                self.write_keyword("IN");
30546                self.write(" (");
30547                self.generate_partition_bound_values(this)?;
30548                self.write(")");
30549            }
30550        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30551            // FROM (from_expressions) TO (to_expressions)
30552            self.write_keyword("FROM");
30553            self.write(" (");
30554            self.generate_partition_bound_values(from)?;
30555            self.write(") ");
30556            self.write_keyword("TO");
30557            self.write(" (");
30558            self.generate_partition_bound_values(to)?;
30559            self.write(")");
30560        }
30561        Ok(())
30562    }
30563
30564    /// Generate partition bound values - handles Tuple expressions by outputting
30565    /// contents without wrapping parens (since caller provides the parens)
30566    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30567        if let Expression::Tuple(t) = expr {
30568            for (i, e) in t.expressions.iter().enumerate() {
30569                if i > 0 {
30570                    self.write(", ");
30571                }
30572                self.generate_expression(e)?;
30573            }
30574            Ok(())
30575        } else {
30576            self.generate_expression(expr)
30577        }
30578    }
30579
30580    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30581        // PARTITION BY LIST (partition_expressions) (create_expressions)
30582        self.write_keyword("PARTITION BY LIST");
30583        if let Some(partition_exprs) = &e.partition_expressions {
30584            self.write(" (");
30585            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30586            self.generate_doris_partition_expressions(partition_exprs)?;
30587            self.write(")");
30588        }
30589        if let Some(create_exprs) = &e.create_expressions {
30590            self.write(" (");
30591            // Unwrap Tuple for partition definitions
30592            self.generate_doris_partition_definitions(create_exprs)?;
30593            self.write(")");
30594        }
30595        Ok(())
30596    }
30597
30598    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30599        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30600        self.write_keyword("PARTITION BY RANGE");
30601        if let Some(partition_exprs) = &e.partition_expressions {
30602            self.write(" (");
30603            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30604            self.generate_doris_partition_expressions(partition_exprs)?;
30605            self.write(")");
30606        }
30607        if let Some(create_exprs) = &e.create_expressions {
30608            self.write(" (");
30609            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30610            self.generate_doris_partition_definitions(create_exprs)?;
30611            self.write(")");
30612        }
30613        Ok(())
30614    }
30615
30616    /// Generate Doris partition column expressions (unwrap Tuple)
30617    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30618        if let Expression::Tuple(t) = expr {
30619            for (i, e) in t.expressions.iter().enumerate() {
30620                if i > 0 {
30621                    self.write(", ");
30622                }
30623                self.generate_expression(e)?;
30624            }
30625        } else {
30626            self.generate_expression(expr)?;
30627        }
30628        Ok(())
30629    }
30630
30631    /// Generate Doris partition definitions (comma-separated Partition expressions)
30632    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30633        match expr {
30634            Expression::Tuple(t) => {
30635                // Multiple partitions, comma-separated
30636                for (i, part) in t.expressions.iter().enumerate() {
30637                    if i > 0 {
30638                        self.write(", ");
30639                    }
30640                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30641                    if let Expression::Partition(p) = part {
30642                        for (j, inner) in p.expressions.iter().enumerate() {
30643                            if j > 0 {
30644                                self.write(", ");
30645                            }
30646                            self.generate_expression(inner)?;
30647                        }
30648                    } else {
30649                        self.generate_expression(part)?;
30650                    }
30651                }
30652            }
30653            Expression::PartitionByRangePropertyDynamic(_) => {
30654                // Dynamic partition - FROM/TO/INTERVAL
30655                self.generate_expression(expr)?;
30656            }
30657            _ => {
30658                self.generate_expression(expr)?;
30659            }
30660        }
30661        Ok(())
30662    }
30663
30664    fn generate_partition_by_range_property_dynamic(
30665        &mut self,
30666        e: &PartitionByRangePropertyDynamic,
30667    ) -> Result<()> {
30668        if e.use_start_end {
30669            // StarRocks: START ('val') END ('val') EVERY (expr)
30670            if let Some(start) = &e.start {
30671                self.write_keyword("START");
30672                self.write(" (");
30673                self.generate_expression(start)?;
30674                self.write(")");
30675            }
30676            if let Some(end) = &e.end {
30677                self.write_space();
30678                self.write_keyword("END");
30679                self.write(" (");
30680                self.generate_expression(end)?;
30681                self.write(")");
30682            }
30683            if let Some(every) = &e.every {
30684                self.write_space();
30685                self.write_keyword("EVERY");
30686                self.write(" (");
30687                // Use unquoted interval format for StarRocks
30688                self.generate_doris_interval(every)?;
30689                self.write(")");
30690            }
30691        } else {
30692            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30693            if let Some(start) = &e.start {
30694                self.write_keyword("FROM");
30695                self.write(" (");
30696                self.generate_expression(start)?;
30697                self.write(")");
30698            }
30699            if let Some(end) = &e.end {
30700                self.write_space();
30701                self.write_keyword("TO");
30702                self.write(" (");
30703                self.generate_expression(end)?;
30704                self.write(")");
30705            }
30706            if let Some(every) = &e.every {
30707                self.write_space();
30708                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30709                self.generate_doris_interval(every)?;
30710            }
30711        }
30712        Ok(())
30713    }
30714
30715    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30716    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30717        if let Expression::Interval(interval) = expr {
30718            self.write_keyword("INTERVAL");
30719            if let Some(ref value) = interval.this {
30720                self.write_space();
30721                // If the value is a string literal that looks like a number,
30722                // output it without quotes (matching Python sqlglot's
30723                // partitionbyrangepropertydynamic_sql which converts back to number)
30724                match value {
30725                    Expression::Literal(Literal::String(s))
30726                        if s.chars()
30727                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30728                            && !s.is_empty() =>
30729                    {
30730                        self.write(s);
30731                    }
30732                    _ => {
30733                        self.generate_expression(value)?;
30734                    }
30735                }
30736            }
30737            if let Some(ref unit_spec) = interval.unit {
30738                self.write_space();
30739                self.write_interval_unit_spec(unit_spec)?;
30740            }
30741            Ok(())
30742        } else {
30743            self.generate_expression(expr)
30744        }
30745    }
30746
30747    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30748        // TRUNCATE(expression, this)
30749        self.write_keyword("TRUNCATE");
30750        self.write("(");
30751        self.generate_expression(&e.expression)?;
30752        self.write(", ");
30753        self.generate_expression(&e.this)?;
30754        self.write(")");
30755        Ok(())
30756    }
30757
30758    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30759        // Doris: PARTITION name VALUES IN (val1, val2)
30760        self.write_keyword("PARTITION");
30761        self.write_space();
30762        self.generate_expression(&e.this)?;
30763        self.write_space();
30764        self.write_keyword("VALUES IN");
30765        self.write(" (");
30766        for (i, expr) in e.expressions.iter().enumerate() {
30767            if i > 0 {
30768                self.write(", ");
30769            }
30770            self.generate_expression(expr)?;
30771        }
30772        self.write(")");
30773        Ok(())
30774    }
30775
30776    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30777        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30778        // TSQL ranges have no expressions and just use `this TO expression`
30779        if e.expressions.is_empty() && e.expression.is_some() {
30780            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30781            self.generate_expression(&e.this)?;
30782            self.write_space();
30783            self.write_keyword("TO");
30784            self.write_space();
30785            self.generate_expression(e.expression.as_ref().unwrap())?;
30786            return Ok(());
30787        }
30788
30789        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30790        self.write_keyword("PARTITION");
30791        self.write_space();
30792        self.generate_expression(&e.this)?;
30793        self.write_space();
30794
30795        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30796        if e.expressions.len() == 1 {
30797            // Single value: VALUES LESS THAN (val)
30798            self.write_keyword("VALUES LESS THAN");
30799            self.write(" (");
30800            self.generate_expression(&e.expressions[0])?;
30801            self.write(")");
30802        } else if !e.expressions.is_empty() {
30803            // Multiple values with Tuple: VALUES [(val1), (val2))
30804            self.write_keyword("VALUES");
30805            self.write(" [");
30806            for (i, expr) in e.expressions.iter().enumerate() {
30807                if i > 0 {
30808                    self.write(", ");
30809                }
30810                // If the expr is a Tuple, generate its contents wrapped in parens
30811                if let Expression::Tuple(t) = expr {
30812                    self.write("(");
30813                    for (j, inner) in t.expressions.iter().enumerate() {
30814                        if j > 0 {
30815                            self.write(", ");
30816                        }
30817                        self.generate_expression(inner)?;
30818                    }
30819                    self.write(")");
30820                } else {
30821                    self.write("(");
30822                    self.generate_expression(expr)?;
30823                    self.write(")");
30824                }
30825            }
30826            self.write(")");
30827        }
30828        Ok(())
30829    }
30830
30831    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30832        // BUCKET(this, expression)
30833        self.write_keyword("BUCKET");
30834        self.write("(");
30835        self.generate_expression(&e.this)?;
30836        self.write(", ");
30837        self.generate_expression(&e.expression)?;
30838        self.write(")");
30839        Ok(())
30840    }
30841
30842    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
30843        // BigQuery table property: PARTITION BY expression [, expression ...]
30844        self.write_keyword("PARTITION BY");
30845        self.write_space();
30846        for (i, expr) in e.expressions.iter().enumerate() {
30847            if i > 0 {
30848                self.write(", ");
30849            }
30850            self.generate_expression(expr)?;
30851        }
30852        Ok(())
30853    }
30854
30855    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30856        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30857        if matches!(
30858            self.config.dialect,
30859            Some(crate::dialects::DialectType::Teradata)
30860                | Some(crate::dialects::DialectType::ClickHouse)
30861        ) {
30862            self.write_keyword("PARTITION BY");
30863        } else {
30864            self.write_keyword("PARTITIONED BY");
30865        }
30866        self.write_space();
30867        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30868        if self.config.pretty {
30869            if let Expression::Tuple(ref tuple) = *e.this {
30870                self.write("(");
30871                self.write_newline();
30872                self.indent_level += 1;
30873                for (i, expr) in tuple.expressions.iter().enumerate() {
30874                    if i > 0 {
30875                        self.write(",");
30876                        self.write_newline();
30877                    }
30878                    self.write_indent();
30879                    self.generate_expression(expr)?;
30880                }
30881                self.indent_level -= 1;
30882                self.write_newline();
30883                self.write(")");
30884            } else {
30885                self.generate_expression(&e.this)?;
30886            }
30887        } else {
30888            self.generate_expression(&e.this)?;
30889        }
30890        Ok(())
30891    }
30892
30893    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30894        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30895        self.write_keyword("PARTITION OF");
30896        self.write_space();
30897        self.generate_expression(&e.this)?;
30898        // Check if expression is a PartitionBoundSpec
30899        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30900            self.write_space();
30901            self.write_keyword("FOR VALUES");
30902            self.write_space();
30903            self.generate_expression(&e.expression)?;
30904        } else {
30905            self.write_space();
30906            self.write_keyword("DEFAULT");
30907        }
30908        Ok(())
30909    }
30910
30911    fn generate_period_for_system_time_constraint(
30912        &mut self,
30913        e: &PeriodForSystemTimeConstraint,
30914    ) -> Result<()> {
30915        // PERIOD FOR SYSTEM_TIME (this, expression)
30916        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30917        self.write(" (");
30918        self.generate_expression(&e.this)?;
30919        self.write(", ");
30920        self.generate_expression(&e.expression)?;
30921        self.write(")");
30922        Ok(())
30923    }
30924
30925    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30926        // value AS alias
30927        // The alias can be an identifier or an expression (e.g., string concatenation)
30928        self.generate_expression(&e.this)?;
30929        self.write_space();
30930        self.write_keyword("AS");
30931        self.write_space();
30932        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30933        if self.config.unpivot_aliases_are_identifiers {
30934            match &e.alias {
30935                Expression::Literal(Literal::String(s)) => {
30936                    // Convert string literal to identifier
30937                    self.generate_identifier(&Identifier::new(s.clone()))?;
30938                }
30939                Expression::Literal(Literal::Number(n)) => {
30940                    // Convert number literal to quoted identifier
30941                    let mut id = Identifier::new(n.clone());
30942                    id.quoted = true;
30943                    self.generate_identifier(&id)?;
30944                }
30945                other => {
30946                    self.generate_expression(other)?;
30947                }
30948            }
30949        } else {
30950            self.generate_expression(&e.alias)?;
30951        }
30952        Ok(())
30953    }
30954
30955    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30956        // ANY or ANY [expression]
30957        self.write_keyword("ANY");
30958        if let Some(this) = &e.this {
30959            self.write_space();
30960            self.generate_expression(this)?;
30961        }
30962        Ok(())
30963    }
30964
30965    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30966        // ML.PREDICT(MODEL this, expression, [params_struct])
30967        self.write_keyword("ML.PREDICT");
30968        self.write("(");
30969        self.write_keyword("MODEL");
30970        self.write_space();
30971        self.generate_expression(&e.this)?;
30972        self.write(", ");
30973        self.generate_expression(&e.expression)?;
30974        if let Some(params) = &e.params_struct {
30975            self.write(", ");
30976            self.generate_expression(params)?;
30977        }
30978        self.write(")");
30979        Ok(())
30980    }
30981
30982    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30983        // PREVIOUS_DAY(this, expression)
30984        self.write_keyword("PREVIOUS_DAY");
30985        self.write("(");
30986        self.generate_expression(&e.this)?;
30987        self.write(", ");
30988        self.generate_expression(&e.expression)?;
30989        self.write(")");
30990        Ok(())
30991    }
30992
30993    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
30994        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
30995        self.write_keyword("PRIMARY KEY");
30996        if let Some(name) = &e.this {
30997            self.write_space();
30998            self.generate_expression(name)?;
30999        }
31000        if !e.expressions.is_empty() {
31001            self.write(" (");
31002            for (i, expr) in e.expressions.iter().enumerate() {
31003                if i > 0 {
31004                    self.write(", ");
31005                }
31006                self.generate_expression(expr)?;
31007            }
31008            self.write(")");
31009        }
31010        if let Some(include) = &e.include {
31011            self.write_space();
31012            self.generate_expression(include)?;
31013        }
31014        if !e.options.is_empty() {
31015            self.write_space();
31016            for (i, opt) in e.options.iter().enumerate() {
31017                if i > 0 {
31018                    self.write_space();
31019                }
31020                self.generate_expression(opt)?;
31021            }
31022        }
31023        Ok(())
31024    }
31025
31026    fn generate_primary_key_column_constraint(
31027        &mut self,
31028        _e: &PrimaryKeyColumnConstraint,
31029    ) -> Result<()> {
31030        // PRIMARY KEY constraint at column level
31031        self.write_keyword("PRIMARY KEY");
31032        Ok(())
31033    }
31034
31035    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
31036        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
31037        self.write_keyword("PATH");
31038        self.write_space();
31039        self.generate_expression(&e.this)?;
31040        Ok(())
31041    }
31042
31043    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
31044        // PROJECTION this (expression)
31045        self.write_keyword("PROJECTION");
31046        self.write_space();
31047        self.generate_expression(&e.this)?;
31048        self.write(" (");
31049        self.generate_expression(&e.expression)?;
31050        self.write(")");
31051        Ok(())
31052    }
31053
31054    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
31055        // Properties list
31056        for (i, prop) in e.expressions.iter().enumerate() {
31057            if i > 0 {
31058                self.write(", ");
31059            }
31060            self.generate_expression(prop)?;
31061        }
31062        Ok(())
31063    }
31064
31065    fn generate_property(&mut self, e: &Property) -> Result<()> {
31066        // name=value
31067        self.generate_expression(&e.this)?;
31068        if let Some(value) = &e.value {
31069            self.write("=");
31070            self.generate_expression(value)?;
31071        }
31072        Ok(())
31073    }
31074
31075    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
31076        self.write_keyword("OPTIONS");
31077        if e.entries.is_empty() {
31078            self.write(" ()");
31079            return Ok(());
31080        }
31081
31082        if self.config.pretty {
31083            self.write(" (");
31084            self.write_newline();
31085            self.indent_level += 1;
31086            for (i, entry) in e.entries.iter().enumerate() {
31087                if i > 0 {
31088                    self.write(",");
31089                    self.write_newline();
31090                }
31091                self.write_indent();
31092                self.generate_identifier(&entry.key)?;
31093                self.write("=");
31094                self.generate_expression(&entry.value)?;
31095            }
31096            self.indent_level -= 1;
31097            self.write_newline();
31098            self.write(")");
31099        } else {
31100            self.write(" (");
31101            for (i, entry) in e.entries.iter().enumerate() {
31102                if i > 0 {
31103                    self.write(", ");
31104                }
31105                self.generate_identifier(&entry.key)?;
31106                self.write("=");
31107                self.generate_expression(&entry.value)?;
31108            }
31109            self.write(")");
31110        }
31111        Ok(())
31112    }
31113
31114    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
31115    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
31116        self.write_keyword("OPTIONS");
31117        self.write(" (");
31118        for (i, opt) in options.iter().enumerate() {
31119            if i > 0 {
31120                self.write(", ");
31121            }
31122            self.generate_option_expression(opt)?;
31123        }
31124        self.write(")");
31125        Ok(())
31126    }
31127
31128    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31129    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31130        self.write_keyword("PROPERTIES");
31131        self.write(" (");
31132        for (i, prop) in properties.iter().enumerate() {
31133            if i > 0 {
31134                self.write(", ");
31135            }
31136            self.generate_option_expression(prop)?;
31137        }
31138        self.write(")");
31139        Ok(())
31140    }
31141
31142    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31143    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31144        self.write_keyword("ENVIRONMENT");
31145        self.write(" (");
31146        for (i, env_item) in environment.iter().enumerate() {
31147            if i > 0 {
31148                self.write(", ");
31149            }
31150            self.generate_environment_expression(env_item)?;
31151        }
31152        self.write(")");
31153        Ok(())
31154    }
31155
31156    /// Generate an environment expression with spaces around =
31157    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31158        match expr {
31159            Expression::Eq(eq) => {
31160                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31161                self.generate_expression(&eq.left)?;
31162                self.write(" = ");
31163                self.generate_expression(&eq.right)?;
31164                Ok(())
31165            }
31166            _ => self.generate_expression(expr),
31167        }
31168    }
31169
31170    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31171    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31172        self.write_keyword("TBLPROPERTIES");
31173        if self.config.pretty {
31174            self.write(" (");
31175            self.write_newline();
31176            self.indent_level += 1;
31177            for (i, opt) in options.iter().enumerate() {
31178                if i > 0 {
31179                    self.write(",");
31180                    self.write_newline();
31181                }
31182                self.write_indent();
31183                self.generate_option_expression(opt)?;
31184            }
31185            self.indent_level -= 1;
31186            self.write_newline();
31187            self.write(")");
31188        } else {
31189            self.write(" (");
31190            for (i, opt) in options.iter().enumerate() {
31191                if i > 0 {
31192                    self.write(", ");
31193                }
31194                self.generate_option_expression(opt)?;
31195            }
31196            self.write(")");
31197        }
31198        Ok(())
31199    }
31200
31201    /// Generate an option expression without spaces around =
31202    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31203        match expr {
31204            Expression::Eq(eq) => {
31205                // Generate key=value without spaces
31206                self.generate_expression(&eq.left)?;
31207                self.write("=");
31208                self.generate_expression(&eq.right)?;
31209                Ok(())
31210            }
31211            _ => self.generate_expression(expr),
31212        }
31213    }
31214
31215    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31216        // Just output the name
31217        self.generate_expression(&e.this)?;
31218        Ok(())
31219    }
31220
31221    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31222        // PUT source_file @stage [options]
31223        self.write_keyword("PUT");
31224        self.write_space();
31225
31226        // Source file path - preserve original quoting
31227        if e.source_quoted {
31228            self.write("'");
31229            self.write(&e.source);
31230            self.write("'");
31231        } else {
31232            self.write(&e.source);
31233        }
31234
31235        self.write_space();
31236
31237        // Target stage reference - output the string directly (includes @)
31238        if let Expression::Literal(Literal::String(s)) = &e.target {
31239            self.write(s);
31240        } else {
31241            self.generate_expression(&e.target)?;
31242        }
31243
31244        // Optional parameters: KEY=VALUE
31245        for param in &e.params {
31246            self.write_space();
31247            self.write(&param.name);
31248            if let Some(ref value) = param.value {
31249                self.write("=");
31250                self.generate_expression(value)?;
31251            }
31252        }
31253
31254        Ok(())
31255    }
31256
31257    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31258        // QUANTILE(this, quantile)
31259        self.write_keyword("QUANTILE");
31260        self.write("(");
31261        self.generate_expression(&e.this)?;
31262        if let Some(quantile) = &e.quantile {
31263            self.write(", ");
31264            self.generate_expression(quantile)?;
31265        }
31266        self.write(")");
31267        Ok(())
31268    }
31269
31270    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31271        // QUERY_BAND = this [UPDATE] [FOR scope]
31272        if matches!(
31273            self.config.dialect,
31274            Some(crate::dialects::DialectType::Teradata)
31275        ) {
31276            self.write_keyword("SET");
31277            self.write_space();
31278        }
31279        self.write_keyword("QUERY_BAND");
31280        self.write(" = ");
31281        self.generate_expression(&e.this)?;
31282        if e.update.is_some() {
31283            self.write_space();
31284            self.write_keyword("UPDATE");
31285        }
31286        if let Some(scope) = &e.scope {
31287            self.write_space();
31288            self.write_keyword("FOR");
31289            self.write_space();
31290            self.generate_expression(scope)?;
31291        }
31292        Ok(())
31293    }
31294
31295    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31296        // this = expression
31297        self.generate_expression(&e.this)?;
31298        if let Some(expression) = &e.expression {
31299            self.write(" = ");
31300            self.generate_expression(expression)?;
31301        }
31302        Ok(())
31303    }
31304
31305    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31306        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31307        self.write_keyword("TRANSFORM");
31308        self.write("(");
31309        for (i, expr) in e.expressions.iter().enumerate() {
31310            if i > 0 {
31311                self.write(", ");
31312            }
31313            self.generate_expression(expr)?;
31314        }
31315        self.write(")");
31316        if let Some(row_format_before) = &e.row_format_before {
31317            self.write_space();
31318            self.generate_expression(row_format_before)?;
31319        }
31320        if let Some(record_writer) = &e.record_writer {
31321            self.write_space();
31322            self.write_keyword("RECORDWRITER");
31323            self.write_space();
31324            self.generate_expression(record_writer)?;
31325        }
31326        if let Some(command_script) = &e.command_script {
31327            self.write_space();
31328            self.write_keyword("USING");
31329            self.write_space();
31330            self.generate_expression(command_script)?;
31331        }
31332        if let Some(schema) = &e.schema {
31333            self.write_space();
31334            self.write_keyword("AS");
31335            self.write_space();
31336            self.generate_expression(schema)?;
31337        }
31338        if let Some(row_format_after) = &e.row_format_after {
31339            self.write_space();
31340            self.generate_expression(row_format_after)?;
31341        }
31342        if let Some(record_reader) = &e.record_reader {
31343            self.write_space();
31344            self.write_keyword("RECORDREADER");
31345            self.write_space();
31346            self.generate_expression(record_reader)?;
31347        }
31348        Ok(())
31349    }
31350
31351    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31352        // RANDN([seed])
31353        self.write_keyword("RANDN");
31354        self.write("(");
31355        if let Some(this) = &e.this {
31356            self.generate_expression(this)?;
31357        }
31358        self.write(")");
31359        Ok(())
31360    }
31361
31362    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31363        // RANDSTR(this, [generator])
31364        self.write_keyword("RANDSTR");
31365        self.write("(");
31366        self.generate_expression(&e.this)?;
31367        if let Some(generator) = &e.generator {
31368            self.write(", ");
31369            self.generate_expression(generator)?;
31370        }
31371        self.write(")");
31372        Ok(())
31373    }
31374
31375    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31376        // RANGE_BUCKET(this, expression)
31377        self.write_keyword("RANGE_BUCKET");
31378        self.write("(");
31379        self.generate_expression(&e.this)?;
31380        self.write(", ");
31381        self.generate_expression(&e.expression)?;
31382        self.write(")");
31383        Ok(())
31384    }
31385
31386    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31387        // RANGE_N(this BETWEEN expressions [EACH each])
31388        self.write_keyword("RANGE_N");
31389        self.write("(");
31390        self.generate_expression(&e.this)?;
31391        self.write_space();
31392        self.write_keyword("BETWEEN");
31393        self.write_space();
31394        for (i, expr) in e.expressions.iter().enumerate() {
31395            if i > 0 {
31396                self.write(", ");
31397            }
31398            self.generate_expression(expr)?;
31399        }
31400        if let Some(each) = &e.each {
31401            self.write_space();
31402            self.write_keyword("EACH");
31403            self.write_space();
31404            self.generate_expression(each)?;
31405        }
31406        self.write(")");
31407        Ok(())
31408    }
31409
31410    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31411        // READ_CSV(this, expressions...)
31412        self.write_keyword("READ_CSV");
31413        self.write("(");
31414        self.generate_expression(&e.this)?;
31415        for expr in &e.expressions {
31416            self.write(", ");
31417            self.generate_expression(expr)?;
31418        }
31419        self.write(")");
31420        Ok(())
31421    }
31422
31423    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31424        // READ_PARQUET(expressions...)
31425        self.write_keyword("READ_PARQUET");
31426        self.write("(");
31427        for (i, expr) in e.expressions.iter().enumerate() {
31428            if i > 0 {
31429                self.write(", ");
31430            }
31431            self.generate_expression(expr)?;
31432        }
31433        self.write(")");
31434        Ok(())
31435    }
31436
31437    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31438        // SEARCH kind FIRST BY this SET expression [USING using]
31439        // or CYCLE this SET expression [USING using]
31440        if e.kind == "CYCLE" {
31441            self.write_keyword("CYCLE");
31442        } else {
31443            self.write_keyword("SEARCH");
31444            self.write_space();
31445            self.write(&e.kind);
31446            self.write_space();
31447            self.write_keyword("FIRST BY");
31448        }
31449        self.write_space();
31450        self.generate_expression(&e.this)?;
31451        self.write_space();
31452        self.write_keyword("SET");
31453        self.write_space();
31454        self.generate_expression(&e.expression)?;
31455        if let Some(using) = &e.using {
31456            self.write_space();
31457            self.write_keyword("USING");
31458            self.write_space();
31459            self.generate_expression(using)?;
31460        }
31461        Ok(())
31462    }
31463
31464    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31465        // REDUCE(this, initial, merge, [finish])
31466        self.write_keyword("REDUCE");
31467        self.write("(");
31468        self.generate_expression(&e.this)?;
31469        if let Some(initial) = &e.initial {
31470            self.write(", ");
31471            self.generate_expression(initial)?;
31472        }
31473        if let Some(merge) = &e.merge {
31474            self.write(", ");
31475            self.generate_expression(merge)?;
31476        }
31477        if let Some(finish) = &e.finish {
31478            self.write(", ");
31479            self.generate_expression(finish)?;
31480        }
31481        self.write(")");
31482        Ok(())
31483    }
31484
31485    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31486        // REFERENCES this (expressions) [options]
31487        self.write_keyword("REFERENCES");
31488        self.write_space();
31489        self.generate_expression(&e.this)?;
31490        if !e.expressions.is_empty() {
31491            self.write(" (");
31492            for (i, expr) in e.expressions.iter().enumerate() {
31493                if i > 0 {
31494                    self.write(", ");
31495                }
31496                self.generate_expression(expr)?;
31497            }
31498            self.write(")");
31499        }
31500        for opt in &e.options {
31501            self.write_space();
31502            self.generate_expression(opt)?;
31503        }
31504        Ok(())
31505    }
31506
31507    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31508        // REFRESH [kind] this
31509        self.write_keyword("REFRESH");
31510        if !e.kind.is_empty() {
31511            self.write_space();
31512            self.write_keyword(&e.kind);
31513        }
31514        self.write_space();
31515        self.generate_expression(&e.this)?;
31516        Ok(())
31517    }
31518
31519    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31520        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31521        self.write_keyword("REFRESH");
31522        self.write_space();
31523        self.write_keyword(&e.method);
31524
31525        if let Some(ref kind) = e.kind {
31526            self.write_space();
31527            self.write_keyword("ON");
31528            self.write_space();
31529            self.write_keyword(kind);
31530
31531            // EVERY n UNIT
31532            if let Some(ref every) = e.every {
31533                self.write_space();
31534                self.write_keyword("EVERY");
31535                self.write_space();
31536                self.generate_expression(every)?;
31537                if let Some(ref unit) = e.unit {
31538                    self.write_space();
31539                    self.write_keyword(unit);
31540                }
31541            }
31542
31543            // STARTS 'datetime'
31544            if let Some(ref starts) = e.starts {
31545                self.write_space();
31546                self.write_keyword("STARTS");
31547                self.write_space();
31548                self.generate_expression(starts)?;
31549            }
31550        }
31551        Ok(())
31552    }
31553
31554    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31555        // REGEXP_COUNT(this, expression, position, parameters)
31556        self.write_keyword("REGEXP_COUNT");
31557        self.write("(");
31558        self.generate_expression(&e.this)?;
31559        self.write(", ");
31560        self.generate_expression(&e.expression)?;
31561        if let Some(position) = &e.position {
31562            self.write(", ");
31563            self.generate_expression(position)?;
31564        }
31565        if let Some(parameters) = &e.parameters {
31566            self.write(", ");
31567            self.generate_expression(parameters)?;
31568        }
31569        self.write(")");
31570        Ok(())
31571    }
31572
31573    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31574        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31575        self.write_keyword("REGEXP_EXTRACT_ALL");
31576        self.write("(");
31577        self.generate_expression(&e.this)?;
31578        self.write(", ");
31579        self.generate_expression(&e.expression)?;
31580        if let Some(group) = &e.group {
31581            self.write(", ");
31582            self.generate_expression(group)?;
31583        }
31584        self.write(")");
31585        Ok(())
31586    }
31587
31588    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31589        // REGEXP_FULL_MATCH(this, expression)
31590        self.write_keyword("REGEXP_FULL_MATCH");
31591        self.write("(");
31592        self.generate_expression(&e.this)?;
31593        self.write(", ");
31594        self.generate_expression(&e.expression)?;
31595        self.write(")");
31596        Ok(())
31597    }
31598
31599    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31600        use crate::dialects::DialectType;
31601        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31602        if matches!(
31603            self.config.dialect,
31604            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31605        ) && e.flag.is_none()
31606        {
31607            self.generate_expression(&e.this)?;
31608            self.write(" ~* ");
31609            self.generate_expression(&e.expression)?;
31610        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31611            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31612            self.write_keyword("REGEXP_LIKE");
31613            self.write("(");
31614            self.generate_expression(&e.this)?;
31615            self.write(", ");
31616            self.generate_expression(&e.expression)?;
31617            self.write(", ");
31618            if let Some(flag) = &e.flag {
31619                self.generate_expression(flag)?;
31620            } else {
31621                self.write("'i'");
31622            }
31623            self.write(")");
31624        } else {
31625            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31626            self.generate_expression(&e.this)?;
31627            self.write_space();
31628            self.write_keyword("REGEXP_ILIKE");
31629            self.write_space();
31630            self.generate_expression(&e.expression)?;
31631            if let Some(flag) = &e.flag {
31632                self.write(", ");
31633                self.generate_expression(flag)?;
31634            }
31635        }
31636        Ok(())
31637    }
31638
31639    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31640        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31641        self.write_keyword("REGEXP_INSTR");
31642        self.write("(");
31643        self.generate_expression(&e.this)?;
31644        self.write(", ");
31645        self.generate_expression(&e.expression)?;
31646        if let Some(position) = &e.position {
31647            self.write(", ");
31648            self.generate_expression(position)?;
31649        }
31650        if let Some(occurrence) = &e.occurrence {
31651            self.write(", ");
31652            self.generate_expression(occurrence)?;
31653        }
31654        if let Some(option) = &e.option {
31655            self.write(", ");
31656            self.generate_expression(option)?;
31657        }
31658        if let Some(parameters) = &e.parameters {
31659            self.write(", ");
31660            self.generate_expression(parameters)?;
31661        }
31662        if let Some(group) = &e.group {
31663            self.write(", ");
31664            self.generate_expression(group)?;
31665        }
31666        self.write(")");
31667        Ok(())
31668    }
31669
31670    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31671        // REGEXP_SPLIT(this, expression, limit)
31672        self.write_keyword("REGEXP_SPLIT");
31673        self.write("(");
31674        self.generate_expression(&e.this)?;
31675        self.write(", ");
31676        self.generate_expression(&e.expression)?;
31677        if let Some(limit) = &e.limit {
31678            self.write(", ");
31679            self.generate_expression(limit)?;
31680        }
31681        self.write(")");
31682        Ok(())
31683    }
31684
31685    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31686        // REGR_AVGX(this, expression)
31687        self.write_keyword("REGR_AVGX");
31688        self.write("(");
31689        self.generate_expression(&e.this)?;
31690        self.write(", ");
31691        self.generate_expression(&e.expression)?;
31692        self.write(")");
31693        Ok(())
31694    }
31695
31696    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31697        // REGR_AVGY(this, expression)
31698        self.write_keyword("REGR_AVGY");
31699        self.write("(");
31700        self.generate_expression(&e.this)?;
31701        self.write(", ");
31702        self.generate_expression(&e.expression)?;
31703        self.write(")");
31704        Ok(())
31705    }
31706
31707    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31708        // REGR_COUNT(this, expression)
31709        self.write_keyword("REGR_COUNT");
31710        self.write("(");
31711        self.generate_expression(&e.this)?;
31712        self.write(", ");
31713        self.generate_expression(&e.expression)?;
31714        self.write(")");
31715        Ok(())
31716    }
31717
31718    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31719        // REGR_INTERCEPT(this, expression)
31720        self.write_keyword("REGR_INTERCEPT");
31721        self.write("(");
31722        self.generate_expression(&e.this)?;
31723        self.write(", ");
31724        self.generate_expression(&e.expression)?;
31725        self.write(")");
31726        Ok(())
31727    }
31728
31729    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31730        // REGR_R2(this, expression)
31731        self.write_keyword("REGR_R2");
31732        self.write("(");
31733        self.generate_expression(&e.this)?;
31734        self.write(", ");
31735        self.generate_expression(&e.expression)?;
31736        self.write(")");
31737        Ok(())
31738    }
31739
31740    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31741        // REGR_SLOPE(this, expression)
31742        self.write_keyword("REGR_SLOPE");
31743        self.write("(");
31744        self.generate_expression(&e.this)?;
31745        self.write(", ");
31746        self.generate_expression(&e.expression)?;
31747        self.write(")");
31748        Ok(())
31749    }
31750
31751    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31752        // REGR_SXX(this, expression)
31753        self.write_keyword("REGR_SXX");
31754        self.write("(");
31755        self.generate_expression(&e.this)?;
31756        self.write(", ");
31757        self.generate_expression(&e.expression)?;
31758        self.write(")");
31759        Ok(())
31760    }
31761
31762    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31763        // REGR_SXY(this, expression)
31764        self.write_keyword("REGR_SXY");
31765        self.write("(");
31766        self.generate_expression(&e.this)?;
31767        self.write(", ");
31768        self.generate_expression(&e.expression)?;
31769        self.write(")");
31770        Ok(())
31771    }
31772
31773    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31774        // REGR_SYY(this, expression)
31775        self.write_keyword("REGR_SYY");
31776        self.write("(");
31777        self.generate_expression(&e.this)?;
31778        self.write(", ");
31779        self.generate_expression(&e.expression)?;
31780        self.write(")");
31781        Ok(())
31782    }
31783
31784    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31785        // REGR_VALX(this, expression)
31786        self.write_keyword("REGR_VALX");
31787        self.write("(");
31788        self.generate_expression(&e.this)?;
31789        self.write(", ");
31790        self.generate_expression(&e.expression)?;
31791        self.write(")");
31792        Ok(())
31793    }
31794
31795    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31796        // REGR_VALY(this, expression)
31797        self.write_keyword("REGR_VALY");
31798        self.write("(");
31799        self.generate_expression(&e.this)?;
31800        self.write(", ");
31801        self.generate_expression(&e.expression)?;
31802        self.write(")");
31803        Ok(())
31804    }
31805
31806    fn generate_remote_with_connection_model_property(
31807        &mut self,
31808        e: &RemoteWithConnectionModelProperty,
31809    ) -> Result<()> {
31810        // REMOTE WITH CONNECTION this
31811        self.write_keyword("REMOTE WITH CONNECTION");
31812        self.write_space();
31813        self.generate_expression(&e.this)?;
31814        Ok(())
31815    }
31816
31817    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31818        // RENAME COLUMN [IF EXISTS] this TO new_name
31819        self.write_keyword("RENAME COLUMN");
31820        if e.exists {
31821            self.write_space();
31822            self.write_keyword("IF EXISTS");
31823        }
31824        self.write_space();
31825        self.generate_expression(&e.this)?;
31826        if let Some(to) = &e.to {
31827            self.write_space();
31828            self.write_keyword("TO");
31829            self.write_space();
31830            self.generate_expression(to)?;
31831        }
31832        Ok(())
31833    }
31834
31835    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31836        // REPLACE PARTITION expression [FROM source]
31837        self.write_keyword("REPLACE PARTITION");
31838        self.write_space();
31839        self.generate_expression(&e.expression)?;
31840        if let Some(source) = &e.source {
31841            self.write_space();
31842            self.write_keyword("FROM");
31843            self.write_space();
31844            self.generate_expression(source)?;
31845        }
31846        Ok(())
31847    }
31848
31849    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31850        // RETURNING expressions [INTO into]
31851        // TSQL and Fabric use OUTPUT instead of RETURNING
31852        let keyword = match self.config.dialect {
31853            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31854            _ => "RETURNING",
31855        };
31856        self.write_keyword(keyword);
31857        self.write_space();
31858        for (i, expr) in e.expressions.iter().enumerate() {
31859            if i > 0 {
31860                self.write(", ");
31861            }
31862            self.generate_expression(expr)?;
31863        }
31864        if let Some(into) = &e.into {
31865            self.write_space();
31866            self.write_keyword("INTO");
31867            self.write_space();
31868            self.generate_expression(into)?;
31869        }
31870        Ok(())
31871    }
31872
31873    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31874        // OUTPUT expressions [INTO into_table]
31875        self.write_space();
31876        self.write_keyword("OUTPUT");
31877        self.write_space();
31878        for (i, expr) in output.columns.iter().enumerate() {
31879            if i > 0 {
31880                self.write(", ");
31881            }
31882            self.generate_expression(expr)?;
31883        }
31884        if let Some(into_table) = &output.into_table {
31885            self.write_space();
31886            self.write_keyword("INTO");
31887            self.write_space();
31888            self.generate_expression(into_table)?;
31889        }
31890        Ok(())
31891    }
31892
31893    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31894        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31895        self.write_keyword("RETURNS");
31896        if e.is_table.is_some() {
31897            self.write_space();
31898            self.write_keyword("TABLE");
31899        }
31900        if let Some(table) = &e.table {
31901            self.write_space();
31902            self.generate_expression(table)?;
31903        } else if let Some(this) = &e.this {
31904            self.write_space();
31905            self.generate_expression(this)?;
31906        }
31907        if e.null.is_some() {
31908            self.write_space();
31909            self.write_keyword("NULL ON NULL INPUT");
31910        }
31911        Ok(())
31912    }
31913
31914    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31915        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31916        self.write_keyword("ROLLBACK");
31917
31918        // TSQL always uses ROLLBACK TRANSACTION
31919        if e.this.is_none()
31920            && matches!(
31921                self.config.dialect,
31922                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31923            )
31924        {
31925            self.write_space();
31926            self.write_keyword("TRANSACTION");
31927        }
31928
31929        // Check if this has TRANSACTION keyword or transaction name
31930        if let Some(this) = &e.this {
31931            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31932            let is_transaction_marker = matches!(
31933                this.as_ref(),
31934                Expression::Identifier(id) if id.name == "TRANSACTION"
31935            );
31936
31937            self.write_space();
31938            self.write_keyword("TRANSACTION");
31939
31940            // If it's a real transaction name, output it
31941            if !is_transaction_marker {
31942                self.write_space();
31943                self.generate_expression(this)?;
31944            }
31945        }
31946
31947        // Output TO savepoint
31948        if let Some(savepoint) = &e.savepoint {
31949            self.write_space();
31950            self.write_keyword("TO");
31951            self.write_space();
31952            self.generate_expression(savepoint)?;
31953        }
31954        Ok(())
31955    }
31956
31957    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31958        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31959        if e.expressions.is_empty() {
31960            self.write_keyword("WITH ROLLUP");
31961        } else {
31962            self.write_keyword("ROLLUP");
31963            self.write("(");
31964            for (i, expr) in e.expressions.iter().enumerate() {
31965                if i > 0 {
31966                    self.write(", ");
31967                }
31968                self.generate_expression(expr)?;
31969            }
31970            self.write(")");
31971        }
31972        Ok(())
31973    }
31974
31975    fn generate_row_format_delimited_property(
31976        &mut self,
31977        e: &RowFormatDelimitedProperty,
31978    ) -> Result<()> {
31979        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31980        self.write_keyword("ROW FORMAT DELIMITED");
31981        if let Some(fields) = &e.fields {
31982            self.write_space();
31983            self.write_keyword("FIELDS TERMINATED BY");
31984            self.write_space();
31985            self.generate_expression(fields)?;
31986        }
31987        if let Some(escaped) = &e.escaped {
31988            self.write_space();
31989            self.write_keyword("ESCAPED BY");
31990            self.write_space();
31991            self.generate_expression(escaped)?;
31992        }
31993        if let Some(items) = &e.collection_items {
31994            self.write_space();
31995            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
31996            self.write_space();
31997            self.generate_expression(items)?;
31998        }
31999        if let Some(keys) = &e.map_keys {
32000            self.write_space();
32001            self.write_keyword("MAP KEYS TERMINATED BY");
32002            self.write_space();
32003            self.generate_expression(keys)?;
32004        }
32005        if let Some(lines) = &e.lines {
32006            self.write_space();
32007            self.write_keyword("LINES TERMINATED BY");
32008            self.write_space();
32009            self.generate_expression(lines)?;
32010        }
32011        if let Some(null) = &e.null {
32012            self.write_space();
32013            self.write_keyword("NULL DEFINED AS");
32014            self.write_space();
32015            self.generate_expression(null)?;
32016        }
32017        if let Some(serde) = &e.serde {
32018            self.write_space();
32019            self.generate_expression(serde)?;
32020        }
32021        Ok(())
32022    }
32023
32024    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
32025        // ROW FORMAT this
32026        self.write_keyword("ROW FORMAT");
32027        self.write_space();
32028        self.generate_expression(&e.this)?;
32029        Ok(())
32030    }
32031
32032    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
32033        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
32034        self.write_keyword("ROW FORMAT SERDE");
32035        self.write_space();
32036        self.generate_expression(&e.this)?;
32037        if let Some(props) = &e.serde_properties {
32038            self.write_space();
32039            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
32040            self.generate_expression(props)?;
32041        }
32042        Ok(())
32043    }
32044
32045    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
32046        // SHA2(this, length)
32047        self.write_keyword("SHA2");
32048        self.write("(");
32049        self.generate_expression(&e.this)?;
32050        if let Some(length) = e.length {
32051            self.write(", ");
32052            self.write(&length.to_string());
32053        }
32054        self.write(")");
32055        Ok(())
32056    }
32057
32058    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
32059        // SHA2_DIGEST(this, length)
32060        self.write_keyword("SHA2_DIGEST");
32061        self.write("(");
32062        self.generate_expression(&e.this)?;
32063        if let Some(length) = e.length {
32064            self.write(", ");
32065            self.write(&length.to_string());
32066        }
32067        self.write(")");
32068        Ok(())
32069    }
32070
32071    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
32072        let name = if matches!(
32073            self.config.dialect,
32074            Some(crate::dialects::DialectType::Spark)
32075                | Some(crate::dialects::DialectType::Databricks)
32076        ) {
32077            "TRY_ADD"
32078        } else {
32079            "SAFE_ADD"
32080        };
32081        self.write_keyword(name);
32082        self.write("(");
32083        self.generate_expression(&e.this)?;
32084        self.write(", ");
32085        self.generate_expression(&e.expression)?;
32086        self.write(")");
32087        Ok(())
32088    }
32089
32090    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
32091        // SAFE_DIVIDE(this, expression)
32092        self.write_keyword("SAFE_DIVIDE");
32093        self.write("(");
32094        self.generate_expression(&e.this)?;
32095        self.write(", ");
32096        self.generate_expression(&e.expression)?;
32097        self.write(")");
32098        Ok(())
32099    }
32100
32101    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
32102        let name = if matches!(
32103            self.config.dialect,
32104            Some(crate::dialects::DialectType::Spark)
32105                | Some(crate::dialects::DialectType::Databricks)
32106        ) {
32107            "TRY_MULTIPLY"
32108        } else {
32109            "SAFE_MULTIPLY"
32110        };
32111        self.write_keyword(name);
32112        self.write("(");
32113        self.generate_expression(&e.this)?;
32114        self.write(", ");
32115        self.generate_expression(&e.expression)?;
32116        self.write(")");
32117        Ok(())
32118    }
32119
32120    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
32121        let name = if matches!(
32122            self.config.dialect,
32123            Some(crate::dialects::DialectType::Spark)
32124                | Some(crate::dialects::DialectType::Databricks)
32125        ) {
32126            "TRY_SUBTRACT"
32127        } else {
32128            "SAFE_SUBTRACT"
32129        };
32130        self.write_keyword(name);
32131        self.write("(");
32132        self.generate_expression(&e.this)?;
32133        self.write(", ");
32134        self.generate_expression(&e.expression)?;
32135        self.write(")");
32136        Ok(())
32137    }
32138
32139    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32140    /// METHOD (size UNIT) [REPEATABLE (seed)]
32141    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32142        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32143        if matches!(sample.method, SampleMethod::Bucket) {
32144            self.write(" (");
32145            self.write_keyword("BUCKET");
32146            self.write_space();
32147            if let Some(ref num) = sample.bucket_numerator {
32148                self.generate_expression(num)?;
32149            }
32150            self.write_space();
32151            self.write_keyword("OUT OF");
32152            self.write_space();
32153            if let Some(ref denom) = sample.bucket_denominator {
32154                self.generate_expression(denom)?;
32155            }
32156            if let Some(ref field) = sample.bucket_field {
32157                self.write_space();
32158                self.write_keyword("ON");
32159                self.write_space();
32160                self.generate_expression(field)?;
32161            }
32162            self.write(")");
32163            return Ok(());
32164        }
32165
32166        // Output method name if explicitly specified, or for dialects that always require it
32167        let is_snowflake = matches!(
32168            self.config.dialect,
32169            Some(crate::dialects::DialectType::Snowflake)
32170        );
32171        let is_postgres = matches!(
32172            self.config.dialect,
32173            Some(crate::dialects::DialectType::PostgreSQL)
32174                | Some(crate::dialects::DialectType::Redshift)
32175        );
32176        // Databricks and Spark don't output method names
32177        let is_databricks = matches!(
32178            self.config.dialect,
32179            Some(crate::dialects::DialectType::Databricks)
32180        );
32181        let is_spark = matches!(
32182            self.config.dialect,
32183            Some(crate::dialects::DialectType::Spark)
32184        );
32185        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32186        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32187        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32188        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32189            self.write_space();
32190            if !sample.explicit_method && (is_snowflake || force_method) {
32191                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32192                self.write_keyword("BERNOULLI");
32193            } else {
32194                match sample.method {
32195                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32196                    SampleMethod::System => self.write_keyword("SYSTEM"),
32197                    SampleMethod::Block => self.write_keyword("BLOCK"),
32198                    SampleMethod::Row => self.write_keyword("ROW"),
32199                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32200                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32201                    SampleMethod::Bucket => {} // handled above
32202                }
32203            }
32204        }
32205
32206        // Output size, with or without parentheses depending on dialect
32207        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32208        if emit_size_no_parens {
32209            self.write_space();
32210            match &sample.size {
32211                Expression::Tuple(tuple) => {
32212                    for (i, expr) in tuple.expressions.iter().enumerate() {
32213                        if i > 0 {
32214                            self.write(", ");
32215                        }
32216                        self.generate_expression(expr)?;
32217                    }
32218                }
32219                expr => self.generate_expression(expr)?,
32220            }
32221        } else {
32222            self.write(" (");
32223            self.generate_expression(&sample.size)?;
32224        }
32225
32226        // Determine unit
32227        let is_rows_method = matches!(
32228            sample.method,
32229            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32230        );
32231        let is_percent = matches!(
32232            sample.method,
32233            SampleMethod::Percent
32234                | SampleMethod::System
32235                | SampleMethod::Bernoulli
32236                | SampleMethod::Block
32237        );
32238
32239        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32240        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32241        // For Databricks and Spark, always output PERCENT for percentage samples.
32242        let is_presto = matches!(
32243            self.config.dialect,
32244            Some(crate::dialects::DialectType::Presto)
32245                | Some(crate::dialects::DialectType::Trino)
32246                | Some(crate::dialects::DialectType::Athena)
32247        );
32248        let should_output_unit = if is_databricks || is_spark {
32249            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32250            is_percent || is_rows_method || sample.unit_after_size
32251        } else if is_snowflake || is_postgres || is_presto {
32252            sample.unit_after_size
32253        } else {
32254            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32255        };
32256
32257        if should_output_unit {
32258            self.write_space();
32259            if sample.is_percent {
32260                self.write_keyword("PERCENT");
32261            } else if is_rows_method && !sample.unit_after_size {
32262                self.write_keyword("ROWS");
32263            } else if sample.unit_after_size {
32264                match sample.method {
32265                    SampleMethod::Percent
32266                    | SampleMethod::System
32267                    | SampleMethod::Bernoulli
32268                    | SampleMethod::Block => {
32269                        self.write_keyword("PERCENT");
32270                    }
32271                    SampleMethod::Row | SampleMethod::Reservoir => {
32272                        self.write_keyword("ROWS");
32273                    }
32274                    _ => self.write_keyword("ROWS"),
32275                }
32276            } else {
32277                self.write_keyword("PERCENT");
32278            }
32279        }
32280
32281        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32282            if let Some(ref offset) = sample.offset {
32283                self.write_space();
32284                self.write_keyword("OFFSET");
32285                self.write_space();
32286                self.generate_expression(offset)?;
32287            }
32288        }
32289        if !emit_size_no_parens {
32290            self.write(")");
32291        }
32292
32293        Ok(())
32294    }
32295
32296    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32297        // SAMPLE this (ClickHouse uses SAMPLE BY)
32298        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32299            self.write_keyword("SAMPLE BY");
32300        } else {
32301            self.write_keyword("SAMPLE");
32302        }
32303        self.write_space();
32304        self.generate_expression(&e.this)?;
32305        Ok(())
32306    }
32307
32308    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32309        // this (expressions...)
32310        if let Some(this) = &e.this {
32311            self.generate_expression(this)?;
32312        }
32313        if !e.expressions.is_empty() {
32314            // Add space before column list if there's a preceding expression
32315            if e.this.is_some() {
32316                self.write_space();
32317            }
32318            self.write("(");
32319            for (i, expr) in e.expressions.iter().enumerate() {
32320                if i > 0 {
32321                    self.write(", ");
32322                }
32323                self.generate_expression(expr)?;
32324            }
32325            self.write(")");
32326        }
32327        Ok(())
32328    }
32329
32330    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32331        // COMMENT this
32332        self.write_keyword("COMMENT");
32333        self.write_space();
32334        self.generate_expression(&e.this)?;
32335        Ok(())
32336    }
32337
32338    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32339        // [this::]expression
32340        if let Some(this) = &e.this {
32341            self.generate_expression(this)?;
32342            self.write("::");
32343        }
32344        self.generate_expression(&e.expression)?;
32345        Ok(())
32346    }
32347
32348    fn generate_search(&mut self, e: &Search) -> Result<()> {
32349        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32350        self.write_keyword("SEARCH");
32351        self.write("(");
32352        self.generate_expression(&e.this)?;
32353        self.write(", ");
32354        self.generate_expression(&e.expression)?;
32355        if let Some(json_scope) = &e.json_scope {
32356            self.write(", ");
32357            self.generate_expression(json_scope)?;
32358        }
32359        if let Some(analyzer) = &e.analyzer {
32360            self.write(", ");
32361            self.generate_expression(analyzer)?;
32362        }
32363        if let Some(analyzer_options) = &e.analyzer_options {
32364            self.write(", ");
32365            self.generate_expression(analyzer_options)?;
32366        }
32367        if let Some(search_mode) = &e.search_mode {
32368            self.write(", ");
32369            self.generate_expression(search_mode)?;
32370        }
32371        self.write(")");
32372        Ok(())
32373    }
32374
32375    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32376        // SEARCH_IP(this, expression)
32377        self.write_keyword("SEARCH_IP");
32378        self.write("(");
32379        self.generate_expression(&e.this)?;
32380        self.write(", ");
32381        self.generate_expression(&e.expression)?;
32382        self.write(")");
32383        Ok(())
32384    }
32385
32386    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32387        // SECURITY this
32388        self.write_keyword("SECURITY");
32389        self.write_space();
32390        self.generate_expression(&e.this)?;
32391        Ok(())
32392    }
32393
32394    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32395        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32396        self.write("SEMANTIC_VIEW(");
32397
32398        if self.config.pretty {
32399            // Pretty print: each clause on its own line
32400            self.write_newline();
32401            self.indent_level += 1;
32402            self.write_indent();
32403            self.generate_expression(&e.this)?;
32404
32405            if let Some(metrics) = &e.metrics {
32406                self.write_newline();
32407                self.write_indent();
32408                self.write_keyword("METRICS");
32409                self.write_space();
32410                self.generate_semantic_view_tuple(metrics)?;
32411            }
32412            if let Some(dimensions) = &e.dimensions {
32413                self.write_newline();
32414                self.write_indent();
32415                self.write_keyword("DIMENSIONS");
32416                self.write_space();
32417                self.generate_semantic_view_tuple(dimensions)?;
32418            }
32419            if let Some(facts) = &e.facts {
32420                self.write_newline();
32421                self.write_indent();
32422                self.write_keyword("FACTS");
32423                self.write_space();
32424                self.generate_semantic_view_tuple(facts)?;
32425            }
32426            if let Some(where_) = &e.where_ {
32427                self.write_newline();
32428                self.write_indent();
32429                self.write_keyword("WHERE");
32430                self.write_space();
32431                self.generate_expression(where_)?;
32432            }
32433            self.write_newline();
32434            self.indent_level -= 1;
32435            self.write_indent();
32436        } else {
32437            // Compact: all on one line
32438            self.generate_expression(&e.this)?;
32439            if let Some(metrics) = &e.metrics {
32440                self.write_space();
32441                self.write_keyword("METRICS");
32442                self.write_space();
32443                self.generate_semantic_view_tuple(metrics)?;
32444            }
32445            if let Some(dimensions) = &e.dimensions {
32446                self.write_space();
32447                self.write_keyword("DIMENSIONS");
32448                self.write_space();
32449                self.generate_semantic_view_tuple(dimensions)?;
32450            }
32451            if let Some(facts) = &e.facts {
32452                self.write_space();
32453                self.write_keyword("FACTS");
32454                self.write_space();
32455                self.generate_semantic_view_tuple(facts)?;
32456            }
32457            if let Some(where_) = &e.where_ {
32458                self.write_space();
32459                self.write_keyword("WHERE");
32460                self.write_space();
32461                self.generate_expression(where_)?;
32462            }
32463        }
32464        self.write(")");
32465        Ok(())
32466    }
32467
32468    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32469    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32470        if let Expression::Tuple(t) = expr {
32471            for (i, e) in t.expressions.iter().enumerate() {
32472                if i > 0 {
32473                    self.write(", ");
32474                }
32475                self.generate_expression(e)?;
32476            }
32477        } else {
32478            self.generate_expression(expr)?;
32479        }
32480        Ok(())
32481    }
32482
32483    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32484        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32485        if let Some(start) = &e.start {
32486            self.write_keyword("START WITH");
32487            self.write_space();
32488            self.generate_expression(start)?;
32489        }
32490        if let Some(increment) = &e.increment {
32491            self.write_space();
32492            self.write_keyword("INCREMENT BY");
32493            self.write_space();
32494            self.generate_expression(increment)?;
32495        }
32496        if let Some(minvalue) = &e.minvalue {
32497            self.write_space();
32498            self.write_keyword("MINVALUE");
32499            self.write_space();
32500            self.generate_expression(minvalue)?;
32501        }
32502        if let Some(maxvalue) = &e.maxvalue {
32503            self.write_space();
32504            self.write_keyword("MAXVALUE");
32505            self.write_space();
32506            self.generate_expression(maxvalue)?;
32507        }
32508        if let Some(cache) = &e.cache {
32509            self.write_space();
32510            self.write_keyword("CACHE");
32511            self.write_space();
32512            self.generate_expression(cache)?;
32513        }
32514        if let Some(owned) = &e.owned {
32515            self.write_space();
32516            self.write_keyword("OWNED BY");
32517            self.write_space();
32518            self.generate_expression(owned)?;
32519        }
32520        for opt in &e.options {
32521            self.write_space();
32522            self.generate_expression(opt)?;
32523        }
32524        Ok(())
32525    }
32526
32527    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32528        // [WITH] SERDEPROPERTIES (expressions)
32529        if e.with_.is_some() {
32530            self.write_keyword("WITH");
32531            self.write_space();
32532        }
32533        self.write_keyword("SERDEPROPERTIES");
32534        self.write(" (");
32535        for (i, expr) in e.expressions.iter().enumerate() {
32536            if i > 0 {
32537                self.write(", ");
32538            }
32539            // Generate key=value without spaces around =
32540            match expr {
32541                Expression::Eq(eq) => {
32542                    self.generate_expression(&eq.left)?;
32543                    self.write("=");
32544                    self.generate_expression(&eq.right)?;
32545                }
32546                _ => self.generate_expression(expr)?,
32547            }
32548        }
32549        self.write(")");
32550        Ok(())
32551    }
32552
32553    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32554        // @@[kind.]this
32555        self.write("@@");
32556        if let Some(kind) = &e.kind {
32557            self.write(kind);
32558            self.write(".");
32559        }
32560        self.generate_expression(&e.this)?;
32561        Ok(())
32562    }
32563
32564    fn generate_set(&mut self, e: &Set) -> Result<()> {
32565        // SET/UNSET [TAG] expressions
32566        if e.unset.is_some() {
32567            self.write_keyword("UNSET");
32568        } else {
32569            self.write_keyword("SET");
32570        }
32571        if e.tag.is_some() {
32572            self.write_space();
32573            self.write_keyword("TAG");
32574        }
32575        if !e.expressions.is_empty() {
32576            self.write_space();
32577            for (i, expr) in e.expressions.iter().enumerate() {
32578                if i > 0 {
32579                    self.write(", ");
32580                }
32581                self.generate_expression(expr)?;
32582            }
32583        }
32584        Ok(())
32585    }
32586
32587    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32588        // SET this or SETCONFIG this
32589        self.write_keyword("SET");
32590        self.write_space();
32591        self.generate_expression(&e.this)?;
32592        Ok(())
32593    }
32594
32595    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32596        // [kind] name = value
32597        if let Some(kind) = &e.kind {
32598            self.write_keyword(kind);
32599            self.write_space();
32600        }
32601        self.generate_expression(&e.name)?;
32602        self.write(" = ");
32603        self.generate_expression(&e.value)?;
32604        Ok(())
32605    }
32606
32607    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32608        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32609        if let Some(with_) = &e.with_ {
32610            self.generate_expression(with_)?;
32611            self.write_space();
32612        }
32613        self.generate_expression(&e.this)?;
32614        self.write_space();
32615        // kind should be UNION, INTERSECT, EXCEPT, etc.
32616        if let Some(kind) = &e.kind {
32617            self.write_keyword(kind);
32618        }
32619        if e.distinct {
32620            self.write_space();
32621            self.write_keyword("DISTINCT");
32622        } else {
32623            self.write_space();
32624            self.write_keyword("ALL");
32625        }
32626        if e.by_name.is_some() {
32627            self.write_space();
32628            self.write_keyword("BY NAME");
32629        }
32630        self.write_space();
32631        self.generate_expression(&e.expression)?;
32632        Ok(())
32633    }
32634
32635    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32636        // SET or MULTISET
32637        if e.multi.is_some() {
32638            self.write_keyword("MULTISET");
32639        } else {
32640            self.write_keyword("SET");
32641        }
32642        Ok(())
32643    }
32644
32645    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32646        // SETTINGS expressions
32647        self.write_keyword("SETTINGS");
32648        if self.config.pretty && e.expressions.len() > 1 {
32649            // Pretty print: each setting on its own line, indented
32650            self.indent_level += 1;
32651            for (i, expr) in e.expressions.iter().enumerate() {
32652                if i > 0 {
32653                    self.write(",");
32654                }
32655                self.write_newline();
32656                self.write_indent();
32657                self.generate_expression(expr)?;
32658            }
32659            self.indent_level -= 1;
32660        } else {
32661            self.write_space();
32662            for (i, expr) in e.expressions.iter().enumerate() {
32663                if i > 0 {
32664                    self.write(", ");
32665                }
32666                self.generate_expression(expr)?;
32667            }
32668        }
32669        Ok(())
32670    }
32671
32672    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32673        // SHARING = this
32674        self.write_keyword("SHARING");
32675        if let Some(this) = &e.this {
32676            self.write(" = ");
32677            self.generate_expression(this)?;
32678        }
32679        Ok(())
32680    }
32681
32682    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32683        // Python array slicing: begin:end:step
32684        if let Some(begin) = &e.this {
32685            self.generate_expression(begin)?;
32686        }
32687        self.write(":");
32688        if let Some(end) = &e.expression {
32689            self.generate_expression(end)?;
32690        }
32691        if let Some(step) = &e.step {
32692            self.write(":");
32693            self.generate_expression(step)?;
32694        }
32695        Ok(())
32696    }
32697
32698    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32699        // SORT_ARRAY(this, asc)
32700        self.write_keyword("SORT_ARRAY");
32701        self.write("(");
32702        self.generate_expression(&e.this)?;
32703        if let Some(asc) = &e.asc {
32704            self.write(", ");
32705            self.generate_expression(asc)?;
32706        }
32707        self.write(")");
32708        Ok(())
32709    }
32710
32711    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32712        // SORT BY expressions
32713        self.write_keyword("SORT BY");
32714        self.write_space();
32715        for (i, expr) in e.expressions.iter().enumerate() {
32716            if i > 0 {
32717                self.write(", ");
32718            }
32719            self.generate_ordered(expr)?;
32720        }
32721        Ok(())
32722    }
32723
32724    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32725        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32726        if e.compound.is_some() {
32727            self.write_keyword("COMPOUND");
32728            self.write_space();
32729        }
32730        self.write_keyword("SORTKEY");
32731        self.write("(");
32732        // If this is a Tuple, unwrap its contents to avoid double parentheses
32733        if let Expression::Tuple(t) = e.this.as_ref() {
32734            for (i, expr) in t.expressions.iter().enumerate() {
32735                if i > 0 {
32736                    self.write(", ");
32737                }
32738                self.generate_expression(expr)?;
32739            }
32740        } else {
32741            self.generate_expression(&e.this)?;
32742        }
32743        self.write(")");
32744        Ok(())
32745    }
32746
32747    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32748        // SPLIT_PART(this, delimiter, part_index)
32749        self.write_keyword("SPLIT_PART");
32750        self.write("(");
32751        self.generate_expression(&e.this)?;
32752        if let Some(delimiter) = &e.delimiter {
32753            self.write(", ");
32754            self.generate_expression(delimiter)?;
32755        }
32756        if let Some(part_index) = &e.part_index {
32757            self.write(", ");
32758            self.generate_expression(part_index)?;
32759        }
32760        self.write(")");
32761        Ok(())
32762    }
32763
32764    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32765        // READS SQL DATA or MODIFIES SQL DATA, etc.
32766        self.generate_expression(&e.this)?;
32767        Ok(())
32768    }
32769
32770    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32771        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32772        self.write_keyword("SQL SECURITY");
32773        self.write_space();
32774        self.generate_expression(&e.this)?;
32775        Ok(())
32776    }
32777
32778    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32779        // ST_DISTANCE(this, expression, [use_spheroid])
32780        self.write_keyword("ST_DISTANCE");
32781        self.write("(");
32782        self.generate_expression(&e.this)?;
32783        self.write(", ");
32784        self.generate_expression(&e.expression)?;
32785        if let Some(use_spheroid) = &e.use_spheroid {
32786            self.write(", ");
32787            self.generate_expression(use_spheroid)?;
32788        }
32789        self.write(")");
32790        Ok(())
32791    }
32792
32793    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32794        // ST_POINT(this, expression)
32795        self.write_keyword("ST_POINT");
32796        self.write("(");
32797        self.generate_expression(&e.this)?;
32798        self.write(", ");
32799        self.generate_expression(&e.expression)?;
32800        self.write(")");
32801        Ok(())
32802    }
32803
32804    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32805        // IMMUTABLE, STABLE, VOLATILE
32806        self.generate_expression(&e.this)?;
32807        Ok(())
32808    }
32809
32810    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32811        // STANDARD_HASH(this, [expression])
32812        self.write_keyword("STANDARD_HASH");
32813        self.write("(");
32814        self.generate_expression(&e.this)?;
32815        if let Some(expression) = &e.expression {
32816            self.write(", ");
32817            self.generate_expression(expression)?;
32818        }
32819        self.write(")");
32820        Ok(())
32821    }
32822
32823    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32824        // STORED BY this
32825        self.write_keyword("STORED BY");
32826        self.write_space();
32827        self.generate_expression(&e.this)?;
32828        Ok(())
32829    }
32830
32831    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32832        // STRPOS(this, substr) or STRPOS(this, substr, position)
32833        // Different dialects have different function names
32834        use crate::dialects::DialectType;
32835        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32836            // Snowflake: CHARINDEX(substr, str[, position])
32837            self.write_keyword("CHARINDEX");
32838            self.write("(");
32839            if let Some(substr) = &e.substr {
32840                self.generate_expression(substr)?;
32841                self.write(", ");
32842            }
32843            self.generate_expression(&e.this)?;
32844            if let Some(position) = &e.position {
32845                self.write(", ");
32846                self.generate_expression(position)?;
32847            }
32848            self.write(")");
32849        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32850            self.write_keyword("POSITION");
32851            self.write("(");
32852            self.generate_expression(&e.this)?;
32853            if let Some(substr) = &e.substr {
32854                self.write(", ");
32855                self.generate_expression(substr)?;
32856            }
32857            if let Some(position) = &e.position {
32858                self.write(", ");
32859                self.generate_expression(position)?;
32860            }
32861            if let Some(occurrence) = &e.occurrence {
32862                self.write(", ");
32863                self.generate_expression(occurrence)?;
32864            }
32865            self.write(")");
32866        } else if matches!(
32867            self.config.dialect,
32868            Some(DialectType::SQLite)
32869                | Some(DialectType::Oracle)
32870                | Some(DialectType::BigQuery)
32871                | Some(DialectType::Teradata)
32872        ) {
32873            self.write_keyword("INSTR");
32874            self.write("(");
32875            self.generate_expression(&e.this)?;
32876            if let Some(substr) = &e.substr {
32877                self.write(", ");
32878                self.generate_expression(substr)?;
32879            }
32880            if let Some(position) = &e.position {
32881                self.write(", ");
32882                self.generate_expression(position)?;
32883            } else if e.occurrence.is_some() {
32884                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32885                // Default start position is 1
32886                self.write(", 1");
32887            }
32888            if let Some(occurrence) = &e.occurrence {
32889                self.write(", ");
32890                self.generate_expression(occurrence)?;
32891            }
32892            self.write(")");
32893        } else if matches!(
32894            self.config.dialect,
32895            Some(DialectType::MySQL)
32896                | Some(DialectType::SingleStore)
32897                | Some(DialectType::Doris)
32898                | Some(DialectType::StarRocks)
32899                | Some(DialectType::Hive)
32900                | Some(DialectType::Spark)
32901                | Some(DialectType::Databricks)
32902        ) {
32903            // LOCATE(substr, str[, position]) - substr first
32904            self.write_keyword("LOCATE");
32905            self.write("(");
32906            if let Some(substr) = &e.substr {
32907                self.generate_expression(substr)?;
32908                self.write(", ");
32909            }
32910            self.generate_expression(&e.this)?;
32911            if let Some(position) = &e.position {
32912                self.write(", ");
32913                self.generate_expression(position)?;
32914            }
32915            self.write(")");
32916        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32917            // CHARINDEX(substr, str[, position])
32918            self.write_keyword("CHARINDEX");
32919            self.write("(");
32920            if let Some(substr) = &e.substr {
32921                self.generate_expression(substr)?;
32922                self.write(", ");
32923            }
32924            self.generate_expression(&e.this)?;
32925            if let Some(position) = &e.position {
32926                self.write(", ");
32927                self.generate_expression(position)?;
32928            }
32929            self.write(")");
32930        } else if matches!(
32931            self.config.dialect,
32932            Some(DialectType::PostgreSQL)
32933                | Some(DialectType::Materialize)
32934                | Some(DialectType::RisingWave)
32935                | Some(DialectType::Redshift)
32936        ) {
32937            // POSITION(substr IN str) syntax
32938            self.write_keyword("POSITION");
32939            self.write("(");
32940            if let Some(substr) = &e.substr {
32941                self.generate_expression(substr)?;
32942                self.write(" IN ");
32943            }
32944            self.generate_expression(&e.this)?;
32945            self.write(")");
32946        } else {
32947            self.write_keyword("STRPOS");
32948            self.write("(");
32949            self.generate_expression(&e.this)?;
32950            if let Some(substr) = &e.substr {
32951                self.write(", ");
32952                self.generate_expression(substr)?;
32953            }
32954            if let Some(position) = &e.position {
32955                self.write(", ");
32956                self.generate_expression(position)?;
32957            }
32958            if let Some(occurrence) = &e.occurrence {
32959                self.write(", ");
32960                self.generate_expression(occurrence)?;
32961            }
32962            self.write(")");
32963        }
32964        Ok(())
32965    }
32966
32967    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32968        match self.config.dialect {
32969            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32970                // TO_DATE(this, java_format)
32971                self.write_keyword("TO_DATE");
32972                self.write("(");
32973                self.generate_expression(&e.this)?;
32974                if let Some(format) = &e.format {
32975                    self.write(", '");
32976                    self.write(&Self::strftime_to_java_format(format));
32977                    self.write("'");
32978                }
32979                self.write(")");
32980            }
32981            Some(DialectType::DuckDB) => {
32982                // CAST(STRPTIME(this, format) AS DATE)
32983                self.write_keyword("CAST");
32984                self.write("(");
32985                self.write_keyword("STRPTIME");
32986                self.write("(");
32987                self.generate_expression(&e.this)?;
32988                if let Some(format) = &e.format {
32989                    self.write(", '");
32990                    self.write(format);
32991                    self.write("'");
32992                }
32993                self.write(")");
32994                self.write_keyword(" AS ");
32995                self.write_keyword("DATE");
32996                self.write(")");
32997            }
32998            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
32999                // TO_DATE(this, pg_format)
33000                self.write_keyword("TO_DATE");
33001                self.write("(");
33002                self.generate_expression(&e.this)?;
33003                if let Some(format) = &e.format {
33004                    self.write(", '");
33005                    self.write(&Self::strftime_to_postgres_format(format));
33006                    self.write("'");
33007                }
33008                self.write(")");
33009            }
33010            Some(DialectType::BigQuery) => {
33011                // PARSE_DATE(format, this) - note: format comes first for BigQuery
33012                self.write_keyword("PARSE_DATE");
33013                self.write("(");
33014                if let Some(format) = &e.format {
33015                    self.write("'");
33016                    self.write(format);
33017                    self.write("'");
33018                    self.write(", ");
33019                }
33020                self.generate_expression(&e.this)?;
33021                self.write(")");
33022            }
33023            Some(DialectType::Teradata) => {
33024                // CAST(this AS DATE FORMAT 'teradata_fmt')
33025                self.write_keyword("CAST");
33026                self.write("(");
33027                self.generate_expression(&e.this)?;
33028                self.write_keyword(" AS ");
33029                self.write_keyword("DATE");
33030                if let Some(format) = &e.format {
33031                    self.write_keyword(" FORMAT ");
33032                    self.write("'");
33033                    self.write(&Self::strftime_to_teradata_format(format));
33034                    self.write("'");
33035                }
33036                self.write(")");
33037            }
33038            _ => {
33039                // STR_TO_DATE(this, format) - MySQL default
33040                self.write_keyword("STR_TO_DATE");
33041                self.write("(");
33042                self.generate_expression(&e.this)?;
33043                if let Some(format) = &e.format {
33044                    self.write(", '");
33045                    self.write(format);
33046                    self.write("'");
33047                }
33048                self.write(")");
33049            }
33050        }
33051        Ok(())
33052    }
33053
33054    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
33055    fn strftime_to_teradata_format(fmt: &str) -> String {
33056        let mut result = fmt.to_string();
33057        result = result.replace("%Y", "YYYY");
33058        result = result.replace("%y", "YY");
33059        result = result.replace("%m", "MM");
33060        result = result.replace("%B", "MMMM");
33061        result = result.replace("%b", "MMM");
33062        result = result.replace("%d", "DD");
33063        result = result.replace("%j", "DDD");
33064        result = result.replace("%H", "HH");
33065        result = result.replace("%M", "MI");
33066        result = result.replace("%S", "SS");
33067        result = result.replace("%f", "SSSSSS");
33068        result = result.replace("%A", "EEEE");
33069        result = result.replace("%a", "EEE");
33070        result
33071    }
33072
33073    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33074    /// Public static version for use by other modules
33075    pub fn strftime_to_java_format_static(fmt: &str) -> String {
33076        Self::strftime_to_java_format(fmt)
33077    }
33078
33079    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33080    fn strftime_to_java_format(fmt: &str) -> String {
33081        let mut result = fmt.to_string();
33082        // Handle non-padded variants BEFORE their padded counterparts
33083        result = result.replace("%-d", "d");
33084        result = result.replace("%-m", "M");
33085        result = result.replace("%-H", "H");
33086        result = result.replace("%-M", "m");
33087        result = result.replace("%-S", "s");
33088        result = result.replace("%Y", "yyyy");
33089        result = result.replace("%y", "yy");
33090        result = result.replace("%m", "MM");
33091        result = result.replace("%B", "MMMM");
33092        result = result.replace("%b", "MMM");
33093        result = result.replace("%d", "dd");
33094        result = result.replace("%j", "DDD");
33095        result = result.replace("%H", "HH");
33096        result = result.replace("%M", "mm");
33097        result = result.replace("%S", "ss");
33098        result = result.replace("%f", "SSSSSS");
33099        result = result.replace("%A", "EEEE");
33100        result = result.replace("%a", "EEE");
33101        result
33102    }
33103
33104    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
33105    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
33106    fn strftime_to_tsql_format(fmt: &str) -> String {
33107        let mut result = fmt.to_string();
33108        // Handle non-padded variants BEFORE their padded counterparts
33109        result = result.replace("%-d", "d");
33110        result = result.replace("%-m", "M");
33111        result = result.replace("%-H", "H");
33112        result = result.replace("%-M", "m");
33113        result = result.replace("%-S", "s");
33114        result = result.replace("%Y", "yyyy");
33115        result = result.replace("%y", "yy");
33116        result = result.replace("%m", "MM");
33117        result = result.replace("%B", "MMMM");
33118        result = result.replace("%b", "MMM");
33119        result = result.replace("%d", "dd");
33120        result = result.replace("%j", "DDD");
33121        result = result.replace("%H", "HH");
33122        result = result.replace("%M", "mm");
33123        result = result.replace("%S", "ss");
33124        result = result.replace("%f", "ffffff");
33125        result = result.replace("%A", "dddd");
33126        result = result.replace("%a", "ddd");
33127        result
33128    }
33129
33130    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33131    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33132    fn decompose_json_path(path: &str) -> Vec<String> {
33133        let mut parts = Vec::new();
33134        // Strip leading $ and optional .
33135        let path = if path.starts_with("$.") {
33136            &path[2..]
33137        } else if path.starts_with('$') {
33138            &path[1..]
33139        } else {
33140            path
33141        };
33142        if path.is_empty() {
33143            return parts;
33144        }
33145        let mut current = String::new();
33146        let chars: Vec<char> = path.chars().collect();
33147        let mut i = 0;
33148        while i < chars.len() {
33149            match chars[i] {
33150                '.' => {
33151                    if !current.is_empty() {
33152                        parts.push(current.clone());
33153                        current.clear();
33154                    }
33155                    i += 1;
33156                }
33157                '[' => {
33158                    if !current.is_empty() {
33159                        parts.push(current.clone());
33160                        current.clear();
33161                    }
33162                    i += 1;
33163                    // Read the content inside brackets
33164                    let mut bracket_content = String::new();
33165                    while i < chars.len() && chars[i] != ']' {
33166                        // Skip quotes inside brackets
33167                        if chars[i] == '"' || chars[i] == '\'' {
33168                            let quote = chars[i];
33169                            i += 1;
33170                            while i < chars.len() && chars[i] != quote {
33171                                bracket_content.push(chars[i]);
33172                                i += 1;
33173                            }
33174                            if i < chars.len() {
33175                                i += 1;
33176                            } // skip closing quote
33177                        } else {
33178                            bracket_content.push(chars[i]);
33179                            i += 1;
33180                        }
33181                    }
33182                    if i < chars.len() {
33183                        i += 1;
33184                    } // skip ]
33185                      // Skip wildcard [*] - don't add as a part
33186                    if bracket_content != "*" {
33187                        parts.push(bracket_content);
33188                    }
33189                }
33190                _ => {
33191                    current.push(chars[i]);
33192                    i += 1;
33193                }
33194            }
33195        }
33196        if !current.is_empty() {
33197            parts.push(current);
33198        }
33199        parts
33200    }
33201
33202    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
33203    fn strftime_to_postgres_format(fmt: &str) -> String {
33204        let mut result = fmt.to_string();
33205        // Handle non-padded variants BEFORE their padded counterparts
33206        result = result.replace("%-d", "FMDD");
33207        result = result.replace("%-m", "FMMM");
33208        result = result.replace("%-H", "FMHH24");
33209        result = result.replace("%-M", "FMMI");
33210        result = result.replace("%-S", "FMSS");
33211        result = result.replace("%Y", "YYYY");
33212        result = result.replace("%y", "YY");
33213        result = result.replace("%m", "MM");
33214        result = result.replace("%B", "Month");
33215        result = result.replace("%b", "Mon");
33216        result = result.replace("%d", "DD");
33217        result = result.replace("%j", "DDD");
33218        result = result.replace("%H", "HH24");
33219        result = result.replace("%M", "MI");
33220        result = result.replace("%S", "SS");
33221        result = result.replace("%f", "US");
33222        result = result.replace("%A", "Day");
33223        result = result.replace("%a", "Dy");
33224        result
33225    }
33226
33227    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33228    fn strftime_to_snowflake_format(fmt: &str) -> String {
33229        let mut result = fmt.to_string();
33230        // Handle %-d (non-padded day) before %d (padded day)
33231        result = result.replace("%-d", "dd");
33232        result = result.replace("%-m", "mm"); // non-padded month
33233        result = result.replace("%Y", "yyyy");
33234        result = result.replace("%y", "yy");
33235        result = result.replace("%m", "mm");
33236        result = result.replace("%d", "DD");
33237        result = result.replace("%H", "hh24");
33238        result = result.replace("%M", "mi");
33239        result = result.replace("%S", "ss");
33240        result = result.replace("%f", "ff");
33241        result
33242    }
33243
33244    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33245        // STR_TO_MAP(this, pair_delim, key_value_delim)
33246        self.write_keyword("STR_TO_MAP");
33247        self.write("(");
33248        self.generate_expression(&e.this)?;
33249        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33250        let needs_defaults = matches!(
33251            self.config.dialect,
33252            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33253        );
33254        if let Some(pair_delim) = &e.pair_delim {
33255            self.write(", ");
33256            self.generate_expression(pair_delim)?;
33257        } else if needs_defaults {
33258            self.write(", ','");
33259        }
33260        if let Some(key_value_delim) = &e.key_value_delim {
33261            self.write(", ");
33262            self.generate_expression(key_value_delim)?;
33263        } else if needs_defaults {
33264            self.write(", ':'");
33265        }
33266        self.write(")");
33267        Ok(())
33268    }
33269
33270    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33271        // Detect format style: strftime (starts with %) vs Snowflake/Java
33272        let is_strftime = e.format.contains('%');
33273        // Helper: get strftime format from whatever style is stored
33274        let to_strftime = |f: &str| -> String {
33275            if is_strftime {
33276                f.to_string()
33277            } else {
33278                Self::snowflake_format_to_strftime(f)
33279            }
33280        };
33281        // Helper: get Java format
33282        let to_java = |f: &str| -> String {
33283            if is_strftime {
33284                Self::strftime_to_java_format(f)
33285            } else {
33286                Self::snowflake_format_to_spark(f)
33287            }
33288        };
33289        // Helper: get PG format
33290        let to_pg = |f: &str| -> String {
33291            if is_strftime {
33292                Self::strftime_to_postgres_format(f)
33293            } else {
33294                Self::convert_strptime_to_postgres_format(f)
33295            }
33296        };
33297
33298        match self.config.dialect {
33299            Some(DialectType::Exasol) => {
33300                self.write_keyword("TO_DATE");
33301                self.write("(");
33302                self.generate_expression(&e.this)?;
33303                self.write(", '");
33304                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33305                self.write("'");
33306                self.write(")");
33307            }
33308            Some(DialectType::BigQuery) => {
33309                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33310                let fmt = to_strftime(&e.format);
33311                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33312                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33313                self.write_keyword("PARSE_TIMESTAMP");
33314                self.write("('");
33315                self.write(&fmt);
33316                self.write("', ");
33317                self.generate_expression(&e.this)?;
33318                self.write(")");
33319            }
33320            Some(DialectType::Hive) => {
33321                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33322                // Check both the raw format and the converted format (in case it's already Java)
33323                let java_fmt = to_java(&e.format);
33324                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33325                    || java_fmt == "yyyy-MM-dd"
33326                    || e.format == "yyyy-MM-dd HH:mm:ss"
33327                    || e.format == "yyyy-MM-dd"
33328                {
33329                    self.write_keyword("CAST");
33330                    self.write("(");
33331                    self.generate_expression(&e.this)?;
33332                    self.write(" ");
33333                    self.write_keyword("AS TIMESTAMP");
33334                    self.write(")");
33335                } else {
33336                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33337                    self.write_keyword("CAST");
33338                    self.write("(");
33339                    self.write_keyword("FROM_UNIXTIME");
33340                    self.write("(");
33341                    self.write_keyword("UNIX_TIMESTAMP");
33342                    self.write("(");
33343                    self.generate_expression(&e.this)?;
33344                    self.write(", '");
33345                    self.write(&java_fmt);
33346                    self.write("')");
33347                    self.write(") ");
33348                    self.write_keyword("AS TIMESTAMP");
33349                    self.write(")");
33350                }
33351            }
33352            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33353                // Spark: TO_TIMESTAMP(value, java_format)
33354                let java_fmt = to_java(&e.format);
33355                self.write_keyword("TO_TIMESTAMP");
33356                self.write("(");
33357                self.generate_expression(&e.this)?;
33358                self.write(", '");
33359                self.write(&java_fmt);
33360                self.write("')");
33361            }
33362            Some(DialectType::MySQL) => {
33363                // MySQL: STR_TO_DATE(value, format)
33364                let mut fmt = to_strftime(&e.format);
33365                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33366                fmt = fmt.replace("%-d", "%e");
33367                fmt = fmt.replace("%-m", "%c");
33368                fmt = fmt.replace("%H:%M:%S", "%T");
33369                self.write_keyword("STR_TO_DATE");
33370                self.write("(");
33371                self.generate_expression(&e.this)?;
33372                self.write(", '");
33373                self.write(&fmt);
33374                self.write("')");
33375            }
33376            Some(DialectType::Drill) => {
33377                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33378                let java_fmt = to_java(&e.format);
33379                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33380                let java_fmt = java_fmt.replace('T', "''T''");
33381                self.write_keyword("TO_TIMESTAMP");
33382                self.write("(");
33383                self.generate_expression(&e.this)?;
33384                self.write(", '");
33385                self.write(&java_fmt);
33386                self.write("')");
33387            }
33388            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33389                // Presto: DATE_PARSE(value, strftime_format)
33390                let mut fmt = to_strftime(&e.format);
33391                // Presto uses %e for non-padded day, %T for %H:%M:%S
33392                fmt = fmt.replace("%-d", "%e");
33393                fmt = fmt.replace("%-m", "%c");
33394                fmt = fmt.replace("%H:%M:%S", "%T");
33395                self.write_keyword("DATE_PARSE");
33396                self.write("(");
33397                self.generate_expression(&e.this)?;
33398                self.write(", '");
33399                self.write(&fmt);
33400                self.write("')");
33401            }
33402            Some(DialectType::DuckDB) => {
33403                // DuckDB: STRPTIME(value, strftime_format)
33404                let fmt = to_strftime(&e.format);
33405                self.write_keyword("STRPTIME");
33406                self.write("(");
33407                self.generate_expression(&e.this)?;
33408                self.write(", '");
33409                self.write(&fmt);
33410                self.write("')");
33411            }
33412            Some(DialectType::PostgreSQL)
33413            | Some(DialectType::Redshift)
33414            | Some(DialectType::Materialize) => {
33415                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33416                let pg_fmt = to_pg(&e.format);
33417                self.write_keyword("TO_TIMESTAMP");
33418                self.write("(");
33419                self.generate_expression(&e.this)?;
33420                self.write(", '");
33421                self.write(&pg_fmt);
33422                self.write("')");
33423            }
33424            Some(DialectType::Oracle) => {
33425                // Oracle: TO_TIMESTAMP(value, pg_format)
33426                let pg_fmt = to_pg(&e.format);
33427                self.write_keyword("TO_TIMESTAMP");
33428                self.write("(");
33429                self.generate_expression(&e.this)?;
33430                self.write(", '");
33431                self.write(&pg_fmt);
33432                self.write("')");
33433            }
33434            Some(DialectType::Snowflake) => {
33435                // Snowflake: TO_TIMESTAMP(value, format) - native format
33436                self.write_keyword("TO_TIMESTAMP");
33437                self.write("(");
33438                self.generate_expression(&e.this)?;
33439                self.write(", '");
33440                self.write(&e.format);
33441                self.write("')");
33442            }
33443            _ => {
33444                // Default: STR_TO_TIME(this, format)
33445                self.write_keyword("STR_TO_TIME");
33446                self.write("(");
33447                self.generate_expression(&e.this)?;
33448                self.write(", '");
33449                self.write(&e.format);
33450                self.write("'");
33451                self.write(")");
33452            }
33453        }
33454        Ok(())
33455    }
33456
33457    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33458    fn snowflake_format_to_strftime(format: &str) -> String {
33459        let mut result = String::new();
33460        let chars: Vec<char> = format.chars().collect();
33461        let mut i = 0;
33462        while i < chars.len() {
33463            let remaining = &format[i..];
33464            if remaining.starts_with("yyyy") {
33465                result.push_str("%Y");
33466                i += 4;
33467            } else if remaining.starts_with("yy") {
33468                result.push_str("%y");
33469                i += 2;
33470            } else if remaining.starts_with("mmmm") {
33471                result.push_str("%B"); // full month name
33472                i += 4;
33473            } else if remaining.starts_with("mon") {
33474                result.push_str("%b"); // abbreviated month
33475                i += 3;
33476            } else if remaining.starts_with("mm") {
33477                result.push_str("%m");
33478                i += 2;
33479            } else if remaining.starts_with("DD") {
33480                result.push_str("%d");
33481                i += 2;
33482            } else if remaining.starts_with("dy") {
33483                result.push_str("%a"); // abbreviated day name
33484                i += 2;
33485            } else if remaining.starts_with("hh24") {
33486                result.push_str("%H");
33487                i += 4;
33488            } else if remaining.starts_with("hh12") {
33489                result.push_str("%I");
33490                i += 4;
33491            } else if remaining.starts_with("hh") {
33492                result.push_str("%H");
33493                i += 2;
33494            } else if remaining.starts_with("mi") {
33495                result.push_str("%M");
33496                i += 2;
33497            } else if remaining.starts_with("ss") {
33498                result.push_str("%S");
33499                i += 2;
33500            } else if remaining.starts_with("ff") {
33501                // Fractional seconds
33502                result.push_str("%f");
33503                i += 2;
33504                // Skip digits after ff (ff3, ff6, ff9)
33505                while i < chars.len() && chars[i].is_ascii_digit() {
33506                    i += 1;
33507                }
33508            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33509                result.push_str("%p");
33510                i += 2;
33511            } else if remaining.starts_with("tz") {
33512                result.push_str("%Z");
33513                i += 2;
33514            } else {
33515                result.push(chars[i]);
33516                i += 1;
33517            }
33518        }
33519        result
33520    }
33521
33522    /// Convert Snowflake normalized format to Spark format (Java-style)
33523    fn snowflake_format_to_spark(format: &str) -> String {
33524        let mut result = String::new();
33525        let chars: Vec<char> = format.chars().collect();
33526        let mut i = 0;
33527        while i < chars.len() {
33528            let remaining = &format[i..];
33529            if remaining.starts_with("yyyy") {
33530                result.push_str("yyyy");
33531                i += 4;
33532            } else if remaining.starts_with("yy") {
33533                result.push_str("yy");
33534                i += 2;
33535            } else if remaining.starts_with("mmmm") {
33536                result.push_str("MMMM"); // full month name
33537                i += 4;
33538            } else if remaining.starts_with("mon") {
33539                result.push_str("MMM"); // abbreviated month
33540                i += 3;
33541            } else if remaining.starts_with("mm") {
33542                result.push_str("MM");
33543                i += 2;
33544            } else if remaining.starts_with("DD") {
33545                result.push_str("dd");
33546                i += 2;
33547            } else if remaining.starts_with("dy") {
33548                result.push_str("EEE"); // abbreviated day name
33549                i += 2;
33550            } else if remaining.starts_with("hh24") {
33551                result.push_str("HH");
33552                i += 4;
33553            } else if remaining.starts_with("hh12") {
33554                result.push_str("hh");
33555                i += 4;
33556            } else if remaining.starts_with("hh") {
33557                result.push_str("HH");
33558                i += 2;
33559            } else if remaining.starts_with("mi") {
33560                result.push_str("mm");
33561                i += 2;
33562            } else if remaining.starts_with("ss") {
33563                result.push_str("ss");
33564                i += 2;
33565            } else if remaining.starts_with("ff") {
33566                result.push_str("SSS"); // milliseconds
33567                i += 2;
33568                // Skip digits after ff
33569                while i < chars.len() && chars[i].is_ascii_digit() {
33570                    i += 1;
33571                }
33572            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33573                result.push_str("a");
33574                i += 2;
33575            } else if remaining.starts_with("tz") {
33576                result.push_str("z");
33577                i += 2;
33578            } else {
33579                result.push(chars[i]);
33580                i += 1;
33581            }
33582        }
33583        result
33584    }
33585
33586    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33587        match self.config.dialect {
33588            Some(DialectType::DuckDB) => {
33589                // DuckDB: EPOCH(STRPTIME(value, format))
33590                self.write_keyword("EPOCH");
33591                self.write("(");
33592                self.write_keyword("STRPTIME");
33593                self.write("(");
33594                if let Some(this) = &e.this {
33595                    self.generate_expression(this)?;
33596                }
33597                if let Some(format) = &e.format {
33598                    self.write(", '");
33599                    self.write(format);
33600                    self.write("'");
33601                }
33602                self.write("))");
33603            }
33604            Some(DialectType::Hive) => {
33605                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33606                self.write_keyword("UNIX_TIMESTAMP");
33607                self.write("(");
33608                if let Some(this) = &e.this {
33609                    self.generate_expression(this)?;
33610                }
33611                if let Some(format) = &e.format {
33612                    let java_fmt = Self::strftime_to_java_format(format);
33613                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33614                        self.write(", '");
33615                        self.write(&java_fmt);
33616                        self.write("'");
33617                    }
33618                }
33619                self.write(")");
33620            }
33621            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33622                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33623                self.write_keyword("UNIX_TIMESTAMP");
33624                self.write("(");
33625                if let Some(this) = &e.this {
33626                    self.generate_expression(this)?;
33627                }
33628                if let Some(format) = &e.format {
33629                    self.write(", '");
33630                    self.write(format);
33631                    self.write("'");
33632                }
33633                self.write(")");
33634            }
33635            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33636                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33637                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33638                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33639                let java_fmt = Self::strftime_to_java_format(c_fmt);
33640                self.write_keyword("TO_UNIXTIME");
33641                self.write("(");
33642                self.write_keyword("COALESCE");
33643                self.write("(");
33644                self.write_keyword("TRY");
33645                self.write("(");
33646                self.write_keyword("DATE_PARSE");
33647                self.write("(");
33648                self.write_keyword("CAST");
33649                self.write("(");
33650                if let Some(this) = &e.this {
33651                    self.generate_expression(this)?;
33652                }
33653                self.write(" ");
33654                self.write_keyword("AS VARCHAR");
33655                self.write("), '");
33656                self.write(c_fmt);
33657                self.write("')), ");
33658                self.write_keyword("PARSE_DATETIME");
33659                self.write("(");
33660                self.write_keyword("DATE_FORMAT");
33661                self.write("(");
33662                self.write_keyword("CAST");
33663                self.write("(");
33664                if let Some(this) = &e.this {
33665                    self.generate_expression(this)?;
33666                }
33667                self.write(" ");
33668                self.write_keyword("AS TIMESTAMP");
33669                self.write("), '");
33670                self.write(c_fmt);
33671                self.write("'), '");
33672                self.write(&java_fmt);
33673                self.write("')))");
33674            }
33675            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33676                // Spark: UNIX_TIMESTAMP(value, java_format)
33677                self.write_keyword("UNIX_TIMESTAMP");
33678                self.write("(");
33679                if let Some(this) = &e.this {
33680                    self.generate_expression(this)?;
33681                }
33682                if let Some(format) = &e.format {
33683                    let java_fmt = Self::strftime_to_java_format(format);
33684                    self.write(", '");
33685                    self.write(&java_fmt);
33686                    self.write("'");
33687                }
33688                self.write(")");
33689            }
33690            _ => {
33691                // Default: STR_TO_UNIX(this, format)
33692                self.write_keyword("STR_TO_UNIX");
33693                self.write("(");
33694                if let Some(this) = &e.this {
33695                    self.generate_expression(this)?;
33696                }
33697                if let Some(format) = &e.format {
33698                    self.write(", '");
33699                    self.write(format);
33700                    self.write("'");
33701                }
33702                self.write(")");
33703            }
33704        }
33705        Ok(())
33706    }
33707
33708    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33709        // STRING_TO_ARRAY(this, delimiter, null_string)
33710        self.write_keyword("STRING_TO_ARRAY");
33711        self.write("(");
33712        self.generate_expression(&e.this)?;
33713        if let Some(expression) = &e.expression {
33714            self.write(", ");
33715            self.generate_expression(expression)?;
33716        }
33717        if let Some(null_val) = &e.null {
33718            self.write(", ");
33719            self.generate_expression(null_val)?;
33720        }
33721        self.write(")");
33722        Ok(())
33723    }
33724
33725    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33726        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33727            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33728            self.write_keyword("OBJECT_CONSTRUCT");
33729            self.write("(");
33730            for (i, (name, expr)) in e.fields.iter().enumerate() {
33731                if i > 0 {
33732                    self.write(", ");
33733                }
33734                if let Some(name) = name {
33735                    self.write("'");
33736                    self.write(name);
33737                    self.write("'");
33738                    self.write(", ");
33739                } else {
33740                    self.write("'_");
33741                    self.write(&i.to_string());
33742                    self.write("'");
33743                    self.write(", ");
33744                }
33745                self.generate_expression(expr)?;
33746            }
33747            self.write(")");
33748        } else if self.config.struct_curly_brace_notation {
33749            // DuckDB-style: {'key': value, ...}
33750            self.write("{");
33751            for (i, (name, expr)) in e.fields.iter().enumerate() {
33752                if i > 0 {
33753                    self.write(", ");
33754                }
33755                if let Some(name) = name {
33756                    // Quote the key as a string literal
33757                    self.write("'");
33758                    self.write(name);
33759                    self.write("'");
33760                    self.write(": ");
33761                } else {
33762                    // Unnamed field: use positional key
33763                    self.write("'_");
33764                    self.write(&i.to_string());
33765                    self.write("'");
33766                    self.write(": ");
33767                }
33768                self.generate_expression(expr)?;
33769            }
33770            self.write("}");
33771        } else {
33772            // Standard SQL struct notation
33773            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33774            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33775            let value_as_name = matches!(
33776                self.config.dialect,
33777                Some(DialectType::BigQuery)
33778                    | Some(DialectType::Spark)
33779                    | Some(DialectType::Databricks)
33780                    | Some(DialectType::Hive)
33781            );
33782            self.write_keyword("STRUCT");
33783            self.write("(");
33784            for (i, (name, expr)) in e.fields.iter().enumerate() {
33785                if i > 0 {
33786                    self.write(", ");
33787                }
33788                if let Some(name) = name {
33789                    if value_as_name {
33790                        // STRUCT(value AS name)
33791                        self.generate_expression(expr)?;
33792                        self.write_space();
33793                        self.write_keyword("AS");
33794                        self.write_space();
33795                        // Quote name if it contains spaces or special chars
33796                        let needs_quoting = name.contains(' ') || name.contains('-');
33797                        if needs_quoting {
33798                            if matches!(
33799                                self.config.dialect,
33800                                Some(DialectType::Spark)
33801                                    | Some(DialectType::Databricks)
33802                                    | Some(DialectType::Hive)
33803                            ) {
33804                                self.write("`");
33805                                self.write(name);
33806                                self.write("`");
33807                            } else {
33808                                self.write(name);
33809                            }
33810                        } else {
33811                            self.write(name);
33812                        }
33813                    } else {
33814                        // STRUCT(name AS value)
33815                        self.write(name);
33816                        self.write_space();
33817                        self.write_keyword("AS");
33818                        self.write_space();
33819                        self.generate_expression(expr)?;
33820                    }
33821                } else {
33822                    self.generate_expression(expr)?;
33823                }
33824            }
33825            self.write(")");
33826        }
33827        Ok(())
33828    }
33829
33830    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33831        // STUFF(this, start, length, expression)
33832        self.write_keyword("STUFF");
33833        self.write("(");
33834        self.generate_expression(&e.this)?;
33835        if let Some(start) = &e.start {
33836            self.write(", ");
33837            self.generate_expression(start)?;
33838        }
33839        if let Some(length) = e.length {
33840            self.write(", ");
33841            self.write(&length.to_string());
33842        }
33843        self.write(", ");
33844        self.generate_expression(&e.expression)?;
33845        self.write(")");
33846        Ok(())
33847    }
33848
33849    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33850        // SUBSTRING_INDEX(this, delimiter, count)
33851        self.write_keyword("SUBSTRING_INDEX");
33852        self.write("(");
33853        self.generate_expression(&e.this)?;
33854        if let Some(delimiter) = &e.delimiter {
33855            self.write(", ");
33856            self.generate_expression(delimiter)?;
33857        }
33858        if let Some(count) = &e.count {
33859            self.write(", ");
33860            self.generate_expression(count)?;
33861        }
33862        self.write(")");
33863        Ok(())
33864    }
33865
33866    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33867        // SUMMARIZE [TABLE] this
33868        self.write_keyword("SUMMARIZE");
33869        if e.table.is_some() {
33870            self.write_space();
33871            self.write_keyword("TABLE");
33872        }
33873        self.write_space();
33874        self.generate_expression(&e.this)?;
33875        Ok(())
33876    }
33877
33878    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33879        // SYSTIMESTAMP
33880        self.write_keyword("SYSTIMESTAMP");
33881        Ok(())
33882    }
33883
33884    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33885        // alias (columns...)
33886        if let Some(this) = &e.this {
33887            self.generate_expression(this)?;
33888        }
33889        if !e.columns.is_empty() {
33890            self.write("(");
33891            for (i, col) in e.columns.iter().enumerate() {
33892                if i > 0 {
33893                    self.write(", ");
33894                }
33895                self.generate_expression(col)?;
33896            }
33897            self.write(")");
33898        }
33899        Ok(())
33900    }
33901
33902    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33903        // TABLE(this) [AS alias]
33904        self.write_keyword("TABLE");
33905        self.write("(");
33906        self.generate_expression(&e.this)?;
33907        self.write(")");
33908        if let Some(alias) = &e.alias {
33909            self.write_space();
33910            self.write_keyword("AS");
33911            self.write_space();
33912            self.write(alias);
33913        }
33914        Ok(())
33915    }
33916
33917    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33918        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33919        self.write_keyword("ROWS FROM");
33920        self.write(" (");
33921        for (i, expr) in e.expressions.iter().enumerate() {
33922            if i > 0 {
33923                self.write(", ");
33924            }
33925            // Each expression is either:
33926            // - A plain function (no alias)
33927            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33928            match expr {
33929                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33930                    // First element is the function, second is the TableAlias
33931                    self.generate_expression(&tuple.expressions[0])?;
33932                    self.write_space();
33933                    self.write_keyword("AS");
33934                    self.write_space();
33935                    self.generate_expression(&tuple.expressions[1])?;
33936                }
33937                _ => {
33938                    self.generate_expression(expr)?;
33939                }
33940            }
33941        }
33942        self.write(")");
33943        if e.ordinality {
33944            self.write_space();
33945            self.write_keyword("WITH ORDINALITY");
33946        }
33947        if let Some(alias) = &e.alias {
33948            self.write_space();
33949            self.write_keyword("AS");
33950            self.write_space();
33951            self.generate_expression(alias)?;
33952        }
33953        Ok(())
33954    }
33955
33956    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33957        use crate::dialects::DialectType;
33958
33959        // New wrapper pattern: expression + Sample struct
33960        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33961            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33962            if self.config.alias_post_tablesample {
33963                // Handle Subquery with alias and Alias wrapper
33964                if let Expression::Subquery(ref s) = **this {
33965                    if let Some(ref alias) = s.alias {
33966                        // Create a clone without alias for output
33967                        let mut subquery_no_alias = (**s).clone();
33968                        subquery_no_alias.alias = None;
33969                        subquery_no_alias.column_aliases = Vec::new();
33970                        self.generate_expression(&Expression::Subquery(Box::new(
33971                            subquery_no_alias,
33972                        )))?;
33973                        self.write_space();
33974                        self.write_keyword("TABLESAMPLE");
33975                        self.generate_sample_body(sample)?;
33976                        if let Some(ref seed) = sample.seed {
33977                            self.write_space();
33978                            let use_seed = sample.use_seed_keyword
33979                                && !matches!(
33980                                    self.config.dialect,
33981                                    Some(crate::dialects::DialectType::Databricks)
33982                                        | Some(crate::dialects::DialectType::Spark)
33983                                );
33984                            if use_seed {
33985                                self.write_keyword("SEED");
33986                            } else {
33987                                self.write_keyword("REPEATABLE");
33988                            }
33989                            self.write(" (");
33990                            self.generate_expression(seed)?;
33991                            self.write(")");
33992                        }
33993                        self.write_space();
33994                        self.write_keyword("AS");
33995                        self.write_space();
33996                        self.generate_identifier(alias)?;
33997                        return Ok(());
33998                    }
33999                } else if let Expression::Alias(ref a) = **this {
34000                    // Output the base expression without alias
34001                    self.generate_expression(&a.this)?;
34002                    self.write_space();
34003                    self.write_keyword("TABLESAMPLE");
34004                    self.generate_sample_body(sample)?;
34005                    if let Some(ref seed) = sample.seed {
34006                        self.write_space();
34007                        let use_seed = sample.use_seed_keyword
34008                            && !matches!(
34009                                self.config.dialect,
34010                                Some(crate::dialects::DialectType::Databricks)
34011                                    | Some(crate::dialects::DialectType::Spark)
34012                            );
34013                        if use_seed {
34014                            self.write_keyword("SEED");
34015                        } else {
34016                            self.write_keyword("REPEATABLE");
34017                        }
34018                        self.write(" (");
34019                        self.generate_expression(seed)?;
34020                        self.write(")");
34021                    }
34022                    // Output alias after TABLESAMPLE
34023                    self.write_space();
34024                    self.write_keyword("AS");
34025                    self.write_space();
34026                    self.generate_identifier(&a.alias)?;
34027                    return Ok(());
34028                }
34029            }
34030            // Default: generate wrapped expression first, then TABLESAMPLE
34031            self.generate_expression(this)?;
34032            self.write_space();
34033            self.write_keyword("TABLESAMPLE");
34034            self.generate_sample_body(sample)?;
34035            // Seed for table-level sample
34036            if let Some(ref seed) = sample.seed {
34037                self.write_space();
34038                // Databricks uses REPEATABLE, not SEED
34039                let use_seed = sample.use_seed_keyword
34040                    && !matches!(
34041                        self.config.dialect,
34042                        Some(crate::dialects::DialectType::Databricks)
34043                            | Some(crate::dialects::DialectType::Spark)
34044                    );
34045                if use_seed {
34046                    self.write_keyword("SEED");
34047                } else {
34048                    self.write_keyword("REPEATABLE");
34049                }
34050                self.write(" (");
34051                self.generate_expression(seed)?;
34052                self.write(")");
34053            }
34054            return Ok(());
34055        }
34056
34057        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
34058        self.write_keyword("TABLESAMPLE");
34059        if let Some(method) = &e.method {
34060            self.write_space();
34061            self.write_keyword(method);
34062        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34063            // Snowflake defaults to BERNOULLI when no method is specified
34064            self.write_space();
34065            self.write_keyword("BERNOULLI");
34066        }
34067        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
34068            self.write_space();
34069            self.write_keyword("BUCKET");
34070            self.write_space();
34071            self.generate_expression(numerator)?;
34072            self.write_space();
34073            self.write_keyword("OUT OF");
34074            self.write_space();
34075            self.generate_expression(denominator)?;
34076            if let Some(field) = &e.bucket_field {
34077                self.write_space();
34078                self.write_keyword("ON");
34079                self.write_space();
34080                self.generate_expression(field)?;
34081            }
34082        } else if !e.expressions.is_empty() {
34083            self.write(" (");
34084            for (i, expr) in e.expressions.iter().enumerate() {
34085                if i > 0 {
34086                    self.write(", ");
34087                }
34088                self.generate_expression(expr)?;
34089            }
34090            self.write(")");
34091        } else if let Some(percent) = &e.percent {
34092            self.write(" (");
34093            self.generate_expression(percent)?;
34094            self.write_space();
34095            self.write_keyword("PERCENT");
34096            self.write(")");
34097        }
34098        Ok(())
34099    }
34100
34101    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
34102        // [prefix]this[postfix]
34103        if let Some(prefix) = &e.prefix {
34104            self.generate_expression(prefix)?;
34105        }
34106        if let Some(this) = &e.this {
34107            self.generate_expression(this)?;
34108        }
34109        if let Some(postfix) = &e.postfix {
34110            self.generate_expression(postfix)?;
34111        }
34112        Ok(())
34113    }
34114
34115    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
34116        // TAG (expressions)
34117        self.write_keyword("TAG");
34118        self.write(" (");
34119        for (i, expr) in e.expressions.iter().enumerate() {
34120            if i > 0 {
34121                self.write(", ");
34122            }
34123            self.generate_expression(expr)?;
34124        }
34125        self.write(")");
34126        Ok(())
34127    }
34128
34129    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
34130        // TEMPORARY or TEMP or [this] TEMPORARY
34131        if let Some(this) = &e.this {
34132            self.generate_expression(this)?;
34133            self.write_space();
34134        }
34135        self.write_keyword("TEMPORARY");
34136        Ok(())
34137    }
34138
34139    /// Generate a Time function expression
34140    /// For most dialects: TIME('value')
34141    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
34142        // Standard: TIME(value)
34143        self.write_keyword("TIME");
34144        self.write("(");
34145        self.generate_expression(&e.this)?;
34146        self.write(")");
34147        Ok(())
34148    }
34149
34150    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
34151        // TIME_ADD(this, expression, unit)
34152        self.write_keyword("TIME_ADD");
34153        self.write("(");
34154        self.generate_expression(&e.this)?;
34155        self.write(", ");
34156        self.generate_expression(&e.expression)?;
34157        if let Some(unit) = &e.unit {
34158            self.write(", ");
34159            self.write_keyword(unit);
34160        }
34161        self.write(")");
34162        Ok(())
34163    }
34164
34165    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
34166        // TIME_DIFF(this, expression, unit)
34167        self.write_keyword("TIME_DIFF");
34168        self.write("(");
34169        self.generate_expression(&e.this)?;
34170        self.write(", ");
34171        self.generate_expression(&e.expression)?;
34172        if let Some(unit) = &e.unit {
34173            self.write(", ");
34174            self.write_keyword(unit);
34175        }
34176        self.write(")");
34177        Ok(())
34178    }
34179
34180    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
34181        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
34182        self.write_keyword("TIME_FROM_PARTS");
34183        self.write("(");
34184        let mut first = true;
34185        if let Some(hour) = &e.hour {
34186            self.generate_expression(hour)?;
34187            first = false;
34188        }
34189        if let Some(minute) = &e.min {
34190            if !first {
34191                self.write(", ");
34192            }
34193            self.generate_expression(minute)?;
34194            first = false;
34195        }
34196        if let Some(second) = &e.sec {
34197            if !first {
34198                self.write(", ");
34199            }
34200            self.generate_expression(second)?;
34201            first = false;
34202        }
34203        if let Some(ns) = &e.nano {
34204            if !first {
34205                self.write(", ");
34206            }
34207            self.generate_expression(ns)?;
34208        }
34209        self.write(")");
34210        Ok(())
34211    }
34212
34213    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34214        // TIME_SLICE(this, expression, unit)
34215        self.write_keyword("TIME_SLICE");
34216        self.write("(");
34217        self.generate_expression(&e.this)?;
34218        self.write(", ");
34219        self.generate_expression(&e.expression)?;
34220        self.write(", ");
34221        self.write_keyword(&e.unit);
34222        self.write(")");
34223        Ok(())
34224    }
34225
34226    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34227        // TIME_STR_TO_TIME(this)
34228        self.write_keyword("TIME_STR_TO_TIME");
34229        self.write("(");
34230        self.generate_expression(&e.this)?;
34231        self.write(")");
34232        Ok(())
34233    }
34234
34235    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34236        // TIME_SUB(this, expression, unit)
34237        self.write_keyword("TIME_SUB");
34238        self.write("(");
34239        self.generate_expression(&e.this)?;
34240        self.write(", ");
34241        self.generate_expression(&e.expression)?;
34242        if let Some(unit) = &e.unit {
34243            self.write(", ");
34244            self.write_keyword(unit);
34245        }
34246        self.write(")");
34247        Ok(())
34248    }
34249
34250    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34251        match self.config.dialect {
34252            Some(DialectType::Exasol) => {
34253                // Exasol uses TO_CHAR with Exasol-specific format
34254                self.write_keyword("TO_CHAR");
34255                self.write("(");
34256                self.generate_expression(&e.this)?;
34257                self.write(", '");
34258                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34259                self.write("'");
34260                self.write(")");
34261            }
34262            Some(DialectType::PostgreSQL)
34263            | Some(DialectType::Redshift)
34264            | Some(DialectType::Materialize) => {
34265                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34266                self.write_keyword("TO_CHAR");
34267                self.write("(");
34268                self.generate_expression(&e.this)?;
34269                self.write(", '");
34270                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34271                self.write("'");
34272                self.write(")");
34273            }
34274            Some(DialectType::Oracle) => {
34275                // Oracle uses TO_CHAR with PG-like format
34276                self.write_keyword("TO_CHAR");
34277                self.write("(");
34278                self.generate_expression(&e.this)?;
34279                self.write(", '");
34280                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34281                self.write("'");
34282                self.write(")");
34283            }
34284            Some(DialectType::Drill) => {
34285                // Drill: TO_CHAR with Java format
34286                self.write_keyword("TO_CHAR");
34287                self.write("(");
34288                self.generate_expression(&e.this)?;
34289                self.write(", '");
34290                self.write(&Self::strftime_to_java_format(&e.format));
34291                self.write("'");
34292                self.write(")");
34293            }
34294            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34295                // TSQL: FORMAT(value, format) with .NET-style format
34296                self.write_keyword("FORMAT");
34297                self.write("(");
34298                self.generate_expression(&e.this)?;
34299                self.write(", '");
34300                self.write(&Self::strftime_to_tsql_format(&e.format));
34301                self.write("'");
34302                self.write(")");
34303            }
34304            Some(DialectType::DuckDB) => {
34305                // DuckDB: STRFTIME(value, format) - keeps C format
34306                self.write_keyword("STRFTIME");
34307                self.write("(");
34308                self.generate_expression(&e.this)?;
34309                self.write(", '");
34310                self.write(&e.format);
34311                self.write("'");
34312                self.write(")");
34313            }
34314            Some(DialectType::BigQuery) => {
34315                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34316                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34317                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34318                self.write_keyword("FORMAT_DATE");
34319                self.write("('");
34320                self.write(&fmt);
34321                self.write("', ");
34322                self.generate_expression(&e.this)?;
34323                self.write(")");
34324            }
34325            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34326                // Hive/Spark: DATE_FORMAT(value, java_format)
34327                self.write_keyword("DATE_FORMAT");
34328                self.write("(");
34329                self.generate_expression(&e.this)?;
34330                self.write(", '");
34331                self.write(&Self::strftime_to_java_format(&e.format));
34332                self.write("'");
34333                self.write(")");
34334            }
34335            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34336                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34337                self.write_keyword("DATE_FORMAT");
34338                self.write("(");
34339                self.generate_expression(&e.this)?;
34340                self.write(", '");
34341                self.write(&e.format);
34342                self.write("'");
34343                self.write(")");
34344            }
34345            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34346                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34347                self.write_keyword("DATE_FORMAT");
34348                self.write("(");
34349                self.generate_expression(&e.this)?;
34350                self.write(", '");
34351                self.write(&e.format);
34352                self.write("'");
34353                self.write(")");
34354            }
34355            _ => {
34356                // Default: TIME_TO_STR(this, format)
34357                self.write_keyword("TIME_TO_STR");
34358                self.write("(");
34359                self.generate_expression(&e.this)?;
34360                self.write(", '");
34361                self.write(&e.format);
34362                self.write("'");
34363                self.write(")");
34364            }
34365        }
34366        Ok(())
34367    }
34368
34369    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34370        match self.config.dialect {
34371            Some(DialectType::DuckDB) => {
34372                // DuckDB: EPOCH(x)
34373                self.write_keyword("EPOCH");
34374                self.write("(");
34375                self.generate_expression(&e.this)?;
34376                self.write(")");
34377            }
34378            Some(DialectType::Hive)
34379            | Some(DialectType::Spark)
34380            | Some(DialectType::Databricks)
34381            | Some(DialectType::Doris)
34382            | Some(DialectType::StarRocks)
34383            | Some(DialectType::Drill) => {
34384                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34385                self.write_keyword("UNIX_TIMESTAMP");
34386                self.write("(");
34387                self.generate_expression(&e.this)?;
34388                self.write(")");
34389            }
34390            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34391                // Presto: TO_UNIXTIME(x)
34392                self.write_keyword("TO_UNIXTIME");
34393                self.write("(");
34394                self.generate_expression(&e.this)?;
34395                self.write(")");
34396            }
34397            _ => {
34398                // Default: TIME_TO_UNIX(x)
34399                self.write_keyword("TIME_TO_UNIX");
34400                self.write("(");
34401                self.generate_expression(&e.this)?;
34402                self.write(")");
34403            }
34404        }
34405        Ok(())
34406    }
34407
34408    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34409        match self.config.dialect {
34410            Some(DialectType::Hive) => {
34411                // Hive: TO_DATE(x)
34412                self.write_keyword("TO_DATE");
34413                self.write("(");
34414                self.generate_expression(&e.this)?;
34415                self.write(")");
34416            }
34417            _ => {
34418                // Default: TIME_STR_TO_DATE(x)
34419                self.write_keyword("TIME_STR_TO_DATE");
34420                self.write("(");
34421                self.generate_expression(&e.this)?;
34422                self.write(")");
34423            }
34424        }
34425        Ok(())
34426    }
34427
34428    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34429        // TIME_TRUNC(this, unit)
34430        self.write_keyword("TIME_TRUNC");
34431        self.write("(");
34432        self.generate_expression(&e.this)?;
34433        self.write(", ");
34434        self.write_keyword(&e.unit);
34435        self.write(")");
34436        Ok(())
34437    }
34438
34439    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34440        // Just output the unit name
34441        if let Some(unit) = &e.unit {
34442            self.write_keyword(unit);
34443        }
34444        Ok(())
34445    }
34446
34447    /// Generate a Timestamp function expression
34448    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34449    /// For other dialects: TIMESTAMP('value')
34450    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34451        use crate::dialects::DialectType;
34452        use crate::expressions::Literal;
34453
34454        match self.config.dialect {
34455            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34456            Some(DialectType::Exasol) => {
34457                self.write_keyword("TO_TIMESTAMP");
34458                self.write("(");
34459                // Extract the string value from the expression if it's a string literal
34460                if let Some(this) = &e.this {
34461                    match this.as_ref() {
34462                        Expression::Literal(Literal::String(s)) => {
34463                            self.write("'");
34464                            self.write(s);
34465                            self.write("'");
34466                        }
34467                        _ => {
34468                            self.generate_expression(this)?;
34469                        }
34470                    }
34471                }
34472                self.write(")");
34473            }
34474            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34475            _ => {
34476                self.write_keyword("TIMESTAMP");
34477                self.write("(");
34478                if let Some(this) = &e.this {
34479                    self.generate_expression(this)?;
34480                }
34481                if let Some(zone) = &e.zone {
34482                    self.write(", ");
34483                    self.generate_expression(zone)?;
34484                }
34485                self.write(")");
34486            }
34487        }
34488        Ok(())
34489    }
34490
34491    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34492        // TIMESTAMP_ADD(this, expression, unit)
34493        self.write_keyword("TIMESTAMP_ADD");
34494        self.write("(");
34495        self.generate_expression(&e.this)?;
34496        self.write(", ");
34497        self.generate_expression(&e.expression)?;
34498        if let Some(unit) = &e.unit {
34499            self.write(", ");
34500            self.write_keyword(unit);
34501        }
34502        self.write(")");
34503        Ok(())
34504    }
34505
34506    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34507        // TIMESTAMP_DIFF(this, expression, unit)
34508        self.write_keyword("TIMESTAMP_DIFF");
34509        self.write("(");
34510        self.generate_expression(&e.this)?;
34511        self.write(", ");
34512        self.generate_expression(&e.expression)?;
34513        if let Some(unit) = &e.unit {
34514            self.write(", ");
34515            self.write_keyword(unit);
34516        }
34517        self.write(")");
34518        Ok(())
34519    }
34520
34521    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34522        // TIMESTAMP_FROM_PARTS(this, expression)
34523        self.write_keyword("TIMESTAMP_FROM_PARTS");
34524        self.write("(");
34525        if let Some(this) = &e.this {
34526            self.generate_expression(this)?;
34527        }
34528        if let Some(expression) = &e.expression {
34529            self.write(", ");
34530            self.generate_expression(expression)?;
34531        }
34532        if let Some(zone) = &e.zone {
34533            self.write(", ");
34534            self.generate_expression(zone)?;
34535        }
34536        if let Some(milli) = &e.milli {
34537            self.write(", ");
34538            self.generate_expression(milli)?;
34539        }
34540        self.write(")");
34541        Ok(())
34542    }
34543
34544    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34545        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34546        self.write_keyword("TIMESTAMP_SUB");
34547        self.write("(");
34548        self.generate_expression(&e.this)?;
34549        self.write(", ");
34550        self.write_keyword("INTERVAL");
34551        self.write_space();
34552        self.generate_expression(&e.expression)?;
34553        if let Some(unit) = &e.unit {
34554            self.write_space();
34555            self.write_keyword(unit);
34556        }
34557        self.write(")");
34558        Ok(())
34559    }
34560
34561    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34562        // TIMESTAMP_TZ_FROM_PARTS(...)
34563        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34564        self.write("(");
34565        if let Some(zone) = &e.zone {
34566            self.generate_expression(zone)?;
34567        }
34568        self.write(")");
34569        Ok(())
34570    }
34571
34572    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34573        // TO_BINARY(this, [format])
34574        self.write_keyword("TO_BINARY");
34575        self.write("(");
34576        self.generate_expression(&e.this)?;
34577        if let Some(format) = &e.format {
34578            self.write(", '");
34579            self.write(format);
34580            self.write("'");
34581        }
34582        self.write(")");
34583        Ok(())
34584    }
34585
34586    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34587        // TO_BOOLEAN(this)
34588        self.write_keyword("TO_BOOLEAN");
34589        self.write("(");
34590        self.generate_expression(&e.this)?;
34591        self.write(")");
34592        Ok(())
34593    }
34594
34595    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34596        // TO_CHAR(this, [format], [nlsparam])
34597        self.write_keyword("TO_CHAR");
34598        self.write("(");
34599        self.generate_expression(&e.this)?;
34600        if let Some(format) = &e.format {
34601            self.write(", '");
34602            self.write(format);
34603            self.write("'");
34604        }
34605        if let Some(nlsparam) = &e.nlsparam {
34606            self.write(", ");
34607            self.generate_expression(nlsparam)?;
34608        }
34609        self.write(")");
34610        Ok(())
34611    }
34612
34613    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34614        // TO_DECFLOAT(this, [format])
34615        self.write_keyword("TO_DECFLOAT");
34616        self.write("(");
34617        self.generate_expression(&e.this)?;
34618        if let Some(format) = &e.format {
34619            self.write(", '");
34620            self.write(format);
34621            self.write("'");
34622        }
34623        self.write(")");
34624        Ok(())
34625    }
34626
34627    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34628        // TO_DOUBLE(this, [format])
34629        self.write_keyword("TO_DOUBLE");
34630        self.write("(");
34631        self.generate_expression(&e.this)?;
34632        if let Some(format) = &e.format {
34633            self.write(", '");
34634            self.write(format);
34635            self.write("'");
34636        }
34637        self.write(")");
34638        Ok(())
34639    }
34640
34641    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34642        // TO_FILE(this, path)
34643        self.write_keyword("TO_FILE");
34644        self.write("(");
34645        self.generate_expression(&e.this)?;
34646        if let Some(path) = &e.path {
34647            self.write(", ");
34648            self.generate_expression(path)?;
34649        }
34650        self.write(")");
34651        Ok(())
34652    }
34653
34654    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34655        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34656        // If safe flag is set, output TRY_TO_NUMBER
34657        let is_safe = e.safe.is_some();
34658        if is_safe {
34659            self.write_keyword("TRY_TO_NUMBER");
34660        } else {
34661            self.write_keyword("TO_NUMBER");
34662        }
34663        self.write("(");
34664        self.generate_expression(&e.this)?;
34665        if let Some(format) = &e.format {
34666            self.write(", ");
34667            self.generate_expression(format)?;
34668        }
34669        if let Some(nlsparam) = &e.nlsparam {
34670            self.write(", ");
34671            self.generate_expression(nlsparam)?;
34672        }
34673        if let Some(precision) = &e.precision {
34674            self.write(", ");
34675            self.generate_expression(precision)?;
34676        }
34677        if let Some(scale) = &e.scale {
34678            self.write(", ");
34679            self.generate_expression(scale)?;
34680        }
34681        self.write(")");
34682        Ok(())
34683    }
34684
34685    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34686        // TO_TABLE this
34687        self.write_keyword("TO_TABLE");
34688        self.write_space();
34689        self.generate_expression(&e.this)?;
34690        Ok(())
34691    }
34692
34693    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34694        // Check mark to determine the format
34695        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34696            Expression::Identifier(id) => id.name.clone(),
34697            Expression::Literal(Literal::String(s)) => s.clone(),
34698            _ => String::new(),
34699        });
34700
34701        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34702        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34703        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34704            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34705        });
34706
34707        // For Presto/Trino: always use START TRANSACTION
34708        let use_start_transaction = matches!(
34709            self.config.dialect,
34710            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34711        );
34712        // For most dialects: strip TRANSACTION keyword
34713        let strip_transaction = matches!(
34714            self.config.dialect,
34715            Some(DialectType::Snowflake)
34716                | Some(DialectType::PostgreSQL)
34717                | Some(DialectType::Redshift)
34718                | Some(DialectType::MySQL)
34719                | Some(DialectType::Hive)
34720                | Some(DialectType::Spark)
34721                | Some(DialectType::Databricks)
34722                | Some(DialectType::DuckDB)
34723                | Some(DialectType::Oracle)
34724                | Some(DialectType::Doris)
34725                | Some(DialectType::StarRocks)
34726                | Some(DialectType::Materialize)
34727                | Some(DialectType::ClickHouse)
34728        );
34729
34730        if is_start || use_start_transaction {
34731            // START TRANSACTION [modes]
34732            self.write_keyword("START TRANSACTION");
34733            if let Some(modes) = &e.modes {
34734                self.write_space();
34735                self.generate_expression(modes)?;
34736            }
34737        } else {
34738            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34739            self.write_keyword("BEGIN");
34740
34741            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34742            let is_kind = e.this.as_ref().map_or(false, |t| {
34743                if let Expression::Identifier(id) = t.as_ref() {
34744                    matches!(
34745                        id.name.to_uppercase().as_str(),
34746                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34747                    )
34748                } else {
34749                    false
34750                }
34751            });
34752
34753            // Output kind before TRANSACTION keyword
34754            if is_kind {
34755                if let Some(this) = &e.this {
34756                    self.write_space();
34757                    if let Expression::Identifier(id) = this.as_ref() {
34758                        self.write_keyword(&id.name);
34759                    }
34760                }
34761            }
34762
34763            // Output TRANSACTION keyword if it was present and target supports it
34764            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34765                self.write_space();
34766                self.write_keyword("TRANSACTION");
34767            }
34768
34769            // Output transaction name (not kind)
34770            if !is_kind {
34771                if let Some(this) = &e.this {
34772                    self.write_space();
34773                    self.generate_expression(this)?;
34774                }
34775            }
34776
34777            // Output WITH MARK 'description' for TSQL
34778            if has_with_mark {
34779                self.write_space();
34780                self.write_keyword("WITH MARK");
34781                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34782                    if !desc.is_empty() {
34783                        self.write_space();
34784                        self.write(&format!("'{}'", desc));
34785                    }
34786                }
34787            }
34788
34789            // Output modes (isolation levels, etc.)
34790            if let Some(modes) = &e.modes {
34791                self.write_space();
34792                self.generate_expression(modes)?;
34793            }
34794        }
34795        Ok(())
34796    }
34797
34798    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34799        // TRANSFORM(this, expression)
34800        self.write_keyword("TRANSFORM");
34801        self.write("(");
34802        self.generate_expression(&e.this)?;
34803        self.write(", ");
34804        self.generate_expression(&e.expression)?;
34805        self.write(")");
34806        Ok(())
34807    }
34808
34809    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34810        // TRANSFORM(expressions)
34811        self.write_keyword("TRANSFORM");
34812        self.write("(");
34813        if self.config.pretty && !e.expressions.is_empty() {
34814            self.indent_level += 1;
34815            for (i, expr) in e.expressions.iter().enumerate() {
34816                if i > 0 {
34817                    self.write(",");
34818                }
34819                self.write_newline();
34820                self.write_indent();
34821                self.generate_expression(expr)?;
34822            }
34823            self.indent_level -= 1;
34824            self.write_newline();
34825            self.write(")");
34826        } else {
34827            for (i, expr) in e.expressions.iter().enumerate() {
34828                if i > 0 {
34829                    self.write(", ");
34830                }
34831                self.generate_expression(expr)?;
34832            }
34833            self.write(")");
34834        }
34835        Ok(())
34836    }
34837
34838    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34839        use crate::dialects::DialectType;
34840        // TRANSIENT is Snowflake-specific; skip for other dialects
34841        if let Some(this) = &e.this {
34842            self.generate_expression(this)?;
34843            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34844                self.write_space();
34845            }
34846        }
34847        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34848            self.write_keyword("TRANSIENT");
34849        }
34850        Ok(())
34851    }
34852
34853    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34854        // TRANSLATE(this, from_, to)
34855        self.write_keyword("TRANSLATE");
34856        self.write("(");
34857        self.generate_expression(&e.this)?;
34858        if let Some(from) = &e.from_ {
34859            self.write(", ");
34860            self.generate_expression(from)?;
34861        }
34862        if let Some(to) = &e.to {
34863            self.write(", ");
34864            self.generate_expression(to)?;
34865        }
34866        self.write(")");
34867        Ok(())
34868    }
34869
34870    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34871        // TRANSLATE(this USING expression)
34872        self.write_keyword("TRANSLATE");
34873        self.write("(");
34874        self.generate_expression(&e.this)?;
34875        self.write_space();
34876        self.write_keyword("USING");
34877        self.write_space();
34878        self.generate_expression(&e.expression)?;
34879        if e.with_error.is_some() {
34880            self.write_space();
34881            self.write_keyword("WITH ERROR");
34882        }
34883        self.write(")");
34884        Ok(())
34885    }
34886
34887    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34888        // TRUNCATE TABLE table1, table2, ...
34889        self.write_keyword("TRUNCATE TABLE");
34890        self.write_space();
34891        for (i, expr) in e.expressions.iter().enumerate() {
34892            if i > 0 {
34893                self.write(", ");
34894            }
34895            self.generate_expression(expr)?;
34896        }
34897        Ok(())
34898    }
34899
34900    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34901        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34902        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34903        self.write("(");
34904        self.generate_expression(&e.this)?;
34905        if let Some(alphabet) = &e.alphabet {
34906            self.write(", ");
34907            self.generate_expression(alphabet)?;
34908        }
34909        self.write(")");
34910        Ok(())
34911    }
34912
34913    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34914        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34915        self.write_keyword("TRY_BASE64_DECODE_STRING");
34916        self.write("(");
34917        self.generate_expression(&e.this)?;
34918        if let Some(alphabet) = &e.alphabet {
34919            self.write(", ");
34920            self.generate_expression(alphabet)?;
34921        }
34922        self.write(")");
34923        Ok(())
34924    }
34925
34926    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34927        // TRY_TO_DECFLOAT(this, [format])
34928        self.write_keyword("TRY_TO_DECFLOAT");
34929        self.write("(");
34930        self.generate_expression(&e.this)?;
34931        if let Some(format) = &e.format {
34932            self.write(", '");
34933            self.write(format);
34934            self.write("'");
34935        }
34936        self.write(")");
34937        Ok(())
34938    }
34939
34940    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34941        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34942        self.write_keyword("TS_OR_DS_ADD");
34943        self.write("(");
34944        self.generate_expression(&e.this)?;
34945        self.write(", ");
34946        self.generate_expression(&e.expression)?;
34947        if let Some(unit) = &e.unit {
34948            self.write(", ");
34949            self.write_keyword(unit);
34950        }
34951        if let Some(return_type) = &e.return_type {
34952            self.write(", ");
34953            self.generate_expression(return_type)?;
34954        }
34955        self.write(")");
34956        Ok(())
34957    }
34958
34959    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34960        // TS_OR_DS_DIFF(this, expression, [unit])
34961        self.write_keyword("TS_OR_DS_DIFF");
34962        self.write("(");
34963        self.generate_expression(&e.this)?;
34964        self.write(", ");
34965        self.generate_expression(&e.expression)?;
34966        if let Some(unit) = &e.unit {
34967            self.write(", ");
34968            self.write_keyword(unit);
34969        }
34970        self.write(")");
34971        Ok(())
34972    }
34973
34974    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34975        let default_time_format = "%Y-%m-%d %H:%M:%S";
34976        let default_date_format = "%Y-%m-%d";
34977        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34978            f != default_time_format && f != default_date_format
34979        });
34980
34981        if has_non_default_format {
34982            // With non-default format: dialect-specific handling
34983            let fmt = e.format.as_ref().unwrap();
34984            match self.config.dialect {
34985                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34986                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34987                    // STR_TO_DATE is the MySQL-native form of StrToTime
34988                    let str_to_time = crate::expressions::StrToTime {
34989                        this: Box::new((*e.this).clone()),
34990                        format: fmt.clone(),
34991                        zone: None,
34992                        safe: None,
34993                        target_type: None,
34994                    };
34995                    self.generate_str_to_time(&str_to_time)?;
34996                }
34997                Some(DialectType::Hive)
34998                | Some(DialectType::Spark)
34999                | Some(DialectType::Databricks) => {
35000                    // Hive/Spark: TO_DATE(x, java_fmt)
35001                    self.write_keyword("TO_DATE");
35002                    self.write("(");
35003                    self.generate_expression(&e.this)?;
35004                    self.write(", '");
35005                    self.write(&Self::strftime_to_java_format(fmt));
35006                    self.write("')");
35007                }
35008                Some(DialectType::Snowflake) => {
35009                    // Snowflake: TO_DATE(x, snowflake_fmt)
35010                    self.write_keyword("TO_DATE");
35011                    self.write("(");
35012                    self.generate_expression(&e.this)?;
35013                    self.write(", '");
35014                    self.write(&Self::strftime_to_snowflake_format(fmt));
35015                    self.write("')");
35016                }
35017                Some(DialectType::Doris) => {
35018                    // Doris: TO_DATE(x) - ignores format
35019                    self.write_keyword("TO_DATE");
35020                    self.write("(");
35021                    self.generate_expression(&e.this)?;
35022                    self.write(")");
35023                }
35024                _ => {
35025                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
35026                    self.write_keyword("CAST");
35027                    self.write("(");
35028                    let str_to_time = crate::expressions::StrToTime {
35029                        this: Box::new((*e.this).clone()),
35030                        format: fmt.clone(),
35031                        zone: None,
35032                        safe: None,
35033                        target_type: None,
35034                    };
35035                    self.generate_str_to_time(&str_to_time)?;
35036                    self.write_keyword(" AS ");
35037                    self.write_keyword("DATE");
35038                    self.write(")");
35039                }
35040            }
35041        } else {
35042            // Without format (or default format): simple date conversion
35043            match self.config.dialect {
35044                Some(DialectType::MySQL)
35045                | Some(DialectType::SQLite)
35046                | Some(DialectType::StarRocks) => {
35047                    // MySQL/SQLite/StarRocks: DATE(x)
35048                    self.write_keyword("DATE");
35049                    self.write("(");
35050                    self.generate_expression(&e.this)?;
35051                    self.write(")");
35052                }
35053                Some(DialectType::Hive)
35054                | Some(DialectType::Spark)
35055                | Some(DialectType::Databricks)
35056                | Some(DialectType::Snowflake)
35057                | Some(DialectType::Doris) => {
35058                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
35059                    self.write_keyword("TO_DATE");
35060                    self.write("(");
35061                    self.generate_expression(&e.this)?;
35062                    self.write(")");
35063                }
35064                Some(DialectType::Presto)
35065                | Some(DialectType::Trino)
35066                | Some(DialectType::Athena) => {
35067                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
35068                    self.write_keyword("CAST");
35069                    self.write("(");
35070                    self.write_keyword("CAST");
35071                    self.write("(");
35072                    self.generate_expression(&e.this)?;
35073                    self.write_keyword(" AS ");
35074                    self.write_keyword("TIMESTAMP");
35075                    self.write(")");
35076                    self.write_keyword(" AS ");
35077                    self.write_keyword("DATE");
35078                    self.write(")");
35079                }
35080                Some(DialectType::ClickHouse) => {
35081                    // ClickHouse: CAST(x AS Nullable(DATE))
35082                    self.write_keyword("CAST");
35083                    self.write("(");
35084                    self.generate_expression(&e.this)?;
35085                    self.write_keyword(" AS ");
35086                    self.write("Nullable(DATE)");
35087                    self.write(")");
35088                }
35089                _ => {
35090                    // Default: CAST(x AS DATE)
35091                    self.write_keyword("CAST");
35092                    self.write("(");
35093                    self.generate_expression(&e.this)?;
35094                    self.write_keyword(" AS ");
35095                    self.write_keyword("DATE");
35096                    self.write(")");
35097                }
35098            }
35099        }
35100        Ok(())
35101    }
35102
35103    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
35104        // TS_OR_DS_TO_TIME(this, [format])
35105        self.write_keyword("TS_OR_DS_TO_TIME");
35106        self.write("(");
35107        self.generate_expression(&e.this)?;
35108        if let Some(format) = &e.format {
35109            self.write(", '");
35110            self.write(format);
35111            self.write("'");
35112        }
35113        self.write(")");
35114        Ok(())
35115    }
35116
35117    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
35118        // UNHEX(this, [expression])
35119        self.write_keyword("UNHEX");
35120        self.write("(");
35121        self.generate_expression(&e.this)?;
35122        if let Some(expression) = &e.expression {
35123            self.write(", ");
35124            self.generate_expression(expression)?;
35125        }
35126        self.write(")");
35127        Ok(())
35128    }
35129
35130    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
35131        // U&this [UESCAPE escape]
35132        self.write("U&");
35133        self.generate_expression(&e.this)?;
35134        if let Some(escape) = &e.escape {
35135            self.write_space();
35136            self.write_keyword("UESCAPE");
35137            self.write_space();
35138            self.generate_expression(escape)?;
35139        }
35140        Ok(())
35141    }
35142
35143    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
35144        // UNIFORM(this, expression, [gen], [seed])
35145        self.write_keyword("UNIFORM");
35146        self.write("(");
35147        self.generate_expression(&e.this)?;
35148        self.write(", ");
35149        self.generate_expression(&e.expression)?;
35150        if let Some(gen) = &e.gen {
35151            self.write(", ");
35152            self.generate_expression(gen)?;
35153        }
35154        if let Some(seed) = &e.seed {
35155            self.write(", ");
35156            self.generate_expression(seed)?;
35157        }
35158        self.write(")");
35159        Ok(())
35160    }
35161
35162    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
35163        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
35164        self.write_keyword("UNIQUE");
35165        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
35166        if e.nulls.is_some() {
35167            self.write(" NULLS NOT DISTINCT");
35168        }
35169        if let Some(this) = &e.this {
35170            self.write_space();
35171            self.generate_expression(this)?;
35172        }
35173        if let Some(index_type) = &e.index_type {
35174            self.write(" USING ");
35175            self.generate_expression(index_type)?;
35176        }
35177        if let Some(on_conflict) = &e.on_conflict {
35178            self.write_space();
35179            self.generate_expression(on_conflict)?;
35180        }
35181        for opt in &e.options {
35182            self.write_space();
35183            self.generate_expression(opt)?;
35184        }
35185        Ok(())
35186    }
35187
35188    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
35189        // UNIQUE KEY (expressions)
35190        self.write_keyword("UNIQUE KEY");
35191        self.write(" (");
35192        for (i, expr) in e.expressions.iter().enumerate() {
35193            if i > 0 {
35194                self.write(", ");
35195            }
35196            self.generate_expression(expr)?;
35197        }
35198        self.write(")");
35199        Ok(())
35200    }
35201
35202    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
35203        // ROLLUP (r1(col1, col2), r2(col1))
35204        self.write_keyword("ROLLUP");
35205        self.write(" (");
35206        for (i, index) in e.expressions.iter().enumerate() {
35207            if i > 0 {
35208                self.write(", ");
35209            }
35210            self.generate_identifier(&index.name)?;
35211            self.write("(");
35212            for (j, col) in index.expressions.iter().enumerate() {
35213                if j > 0 {
35214                    self.write(", ");
35215                }
35216                self.generate_identifier(col)?;
35217            }
35218            self.write(")");
35219        }
35220        self.write(")");
35221        Ok(())
35222    }
35223
35224    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35225        match self.config.dialect {
35226            Some(DialectType::DuckDB) => {
35227                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35228                self.write_keyword("STRFTIME");
35229                self.write("(");
35230                self.write_keyword("TO_TIMESTAMP");
35231                self.write("(");
35232                self.generate_expression(&e.this)?;
35233                self.write("), '");
35234                if let Some(format) = &e.format {
35235                    self.write(format);
35236                }
35237                self.write("')");
35238            }
35239            Some(DialectType::Hive) => {
35240                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35241                self.write_keyword("FROM_UNIXTIME");
35242                self.write("(");
35243                self.generate_expression(&e.this)?;
35244                if let Some(format) = &e.format {
35245                    if format != "yyyy-MM-dd HH:mm:ss" {
35246                        self.write(", '");
35247                        self.write(format);
35248                        self.write("'");
35249                    }
35250                }
35251                self.write(")");
35252            }
35253            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35254                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35255                self.write_keyword("DATE_FORMAT");
35256                self.write("(");
35257                self.write_keyword("FROM_UNIXTIME");
35258                self.write("(");
35259                self.generate_expression(&e.this)?;
35260                self.write("), '");
35261                if let Some(format) = &e.format {
35262                    self.write(format);
35263                }
35264                self.write("')");
35265            }
35266            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35267                // Spark: FROM_UNIXTIME(value, format)
35268                self.write_keyword("FROM_UNIXTIME");
35269                self.write("(");
35270                self.generate_expression(&e.this)?;
35271                if let Some(format) = &e.format {
35272                    self.write(", '");
35273                    self.write(format);
35274                    self.write("'");
35275                }
35276                self.write(")");
35277            }
35278            _ => {
35279                // Default: UNIX_TO_STR(this, [format])
35280                self.write_keyword("UNIX_TO_STR");
35281                self.write("(");
35282                self.generate_expression(&e.this)?;
35283                if let Some(format) = &e.format {
35284                    self.write(", '");
35285                    self.write(format);
35286                    self.write("'");
35287                }
35288                self.write(")");
35289            }
35290        }
35291        Ok(())
35292    }
35293
35294    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35295        use crate::dialects::DialectType;
35296        let scale = e.scale.unwrap_or(0); // 0 = seconds
35297
35298        match self.config.dialect {
35299            Some(DialectType::Snowflake) => {
35300                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35301                self.write_keyword("TO_TIMESTAMP");
35302                self.write("(");
35303                self.generate_expression(&e.this)?;
35304                if let Some(s) = e.scale {
35305                    if s > 0 {
35306                        self.write(", ");
35307                        self.write(&s.to_string());
35308                    }
35309                }
35310                self.write(")");
35311            }
35312            Some(DialectType::BigQuery) => {
35313                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35314                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35315                match scale {
35316                    0 => {
35317                        self.write_keyword("TIMESTAMP_SECONDS");
35318                        self.write("(");
35319                        self.generate_expression(&e.this)?;
35320                        self.write(")");
35321                    }
35322                    3 => {
35323                        self.write_keyword("TIMESTAMP_MILLIS");
35324                        self.write("(");
35325                        self.generate_expression(&e.this)?;
35326                        self.write(")");
35327                    }
35328                    6 => {
35329                        self.write_keyword("TIMESTAMP_MICROS");
35330                        self.write("(");
35331                        self.generate_expression(&e.this)?;
35332                        self.write(")");
35333                    }
35334                    _ => {
35335                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35336                        self.write_keyword("TIMESTAMP_SECONDS");
35337                        self.write("(CAST(");
35338                        self.generate_expression(&e.this)?;
35339                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35340                    }
35341                }
35342            }
35343            Some(DialectType::Spark) => {
35344                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35345                // TIMESTAMP_MILLIS(value) for scale=3
35346                // TIMESTAMP_MICROS(value) for scale=6
35347                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35348                match scale {
35349                    0 => {
35350                        self.write_keyword("CAST");
35351                        self.write("(");
35352                        self.write_keyword("FROM_UNIXTIME");
35353                        self.write("(");
35354                        self.generate_expression(&e.this)?;
35355                        self.write(") ");
35356                        self.write_keyword("AS TIMESTAMP");
35357                        self.write(")");
35358                    }
35359                    3 => {
35360                        self.write_keyword("TIMESTAMP_MILLIS");
35361                        self.write("(");
35362                        self.generate_expression(&e.this)?;
35363                        self.write(")");
35364                    }
35365                    6 => {
35366                        self.write_keyword("TIMESTAMP_MICROS");
35367                        self.write("(");
35368                        self.generate_expression(&e.this)?;
35369                        self.write(")");
35370                    }
35371                    _ => {
35372                        self.write_keyword("TIMESTAMP_SECONDS");
35373                        self.write("(");
35374                        self.generate_expression(&e.this)?;
35375                        self.write(&format!(" / POWER(10, {}))", scale));
35376                    }
35377                }
35378            }
35379            Some(DialectType::Databricks) => {
35380                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35381                // TIMESTAMP_MILLIS(value) for scale=3
35382                // TIMESTAMP_MICROS(value) for scale=6
35383                match scale {
35384                    0 => {
35385                        self.write_keyword("CAST");
35386                        self.write("(");
35387                        self.write_keyword("FROM_UNIXTIME");
35388                        self.write("(");
35389                        self.generate_expression(&e.this)?;
35390                        self.write(") ");
35391                        self.write_keyword("AS TIMESTAMP");
35392                        self.write(")");
35393                    }
35394                    3 => {
35395                        self.write_keyword("TIMESTAMP_MILLIS");
35396                        self.write("(");
35397                        self.generate_expression(&e.this)?;
35398                        self.write(")");
35399                    }
35400                    6 => {
35401                        self.write_keyword("TIMESTAMP_MICROS");
35402                        self.write("(");
35403                        self.generate_expression(&e.this)?;
35404                        self.write(")");
35405                    }
35406                    _ => {
35407                        self.write_keyword("TIMESTAMP_SECONDS");
35408                        self.write("(");
35409                        self.generate_expression(&e.this)?;
35410                        self.write(&format!(" / POWER(10, {}))", scale));
35411                    }
35412                }
35413            }
35414            Some(DialectType::Hive) => {
35415                // Hive: FROM_UNIXTIME(value)
35416                if scale == 0 {
35417                    self.write_keyword("FROM_UNIXTIME");
35418                    self.write("(");
35419                    self.generate_expression(&e.this)?;
35420                    self.write(")");
35421                } else {
35422                    self.write_keyword("FROM_UNIXTIME");
35423                    self.write("(");
35424                    self.generate_expression(&e.this)?;
35425                    self.write(&format!(" / POWER(10, {})", scale));
35426                    self.write(")");
35427                }
35428            }
35429            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35430                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35431                // FROM_UNIXTIME(value) for scale=0
35432                if scale == 0 {
35433                    self.write_keyword("FROM_UNIXTIME");
35434                    self.write("(");
35435                    self.generate_expression(&e.this)?;
35436                    self.write(")");
35437                } else {
35438                    self.write_keyword("FROM_UNIXTIME");
35439                    self.write("(CAST(");
35440                    self.generate_expression(&e.this)?;
35441                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35442                }
35443            }
35444            Some(DialectType::DuckDB) => {
35445                // DuckDB: TO_TIMESTAMP(value) for scale=0
35446                // EPOCH_MS(value) for scale=3
35447                // MAKE_TIMESTAMP(value) for scale=6
35448                match scale {
35449                    0 => {
35450                        self.write_keyword("TO_TIMESTAMP");
35451                        self.write("(");
35452                        self.generate_expression(&e.this)?;
35453                        self.write(")");
35454                    }
35455                    3 => {
35456                        self.write_keyword("EPOCH_MS");
35457                        self.write("(");
35458                        self.generate_expression(&e.this)?;
35459                        self.write(")");
35460                    }
35461                    6 => {
35462                        self.write_keyword("MAKE_TIMESTAMP");
35463                        self.write("(");
35464                        self.generate_expression(&e.this)?;
35465                        self.write(")");
35466                    }
35467                    _ => {
35468                        self.write_keyword("TO_TIMESTAMP");
35469                        self.write("(");
35470                        self.generate_expression(&e.this)?;
35471                        self.write(&format!(" / POWER(10, {}))", scale));
35472                        self.write_keyword(" AT TIME ZONE");
35473                        self.write(" 'UTC'");
35474                    }
35475                }
35476            }
35477            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35478                // Doris/StarRocks: FROM_UNIXTIME(value)
35479                self.write_keyword("FROM_UNIXTIME");
35480                self.write("(");
35481                self.generate_expression(&e.this)?;
35482                self.write(")");
35483            }
35484            Some(DialectType::Oracle) => {
35485                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35486                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35487                self.generate_expression(&e.this)?;
35488                self.write(" / 86400)");
35489            }
35490            Some(DialectType::Redshift) => {
35491                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35492                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35493                self.write("(TIMESTAMP 'epoch' + ");
35494                if scale == 0 {
35495                    self.generate_expression(&e.this)?;
35496                } else {
35497                    self.write("(");
35498                    self.generate_expression(&e.this)?;
35499                    self.write(&format!(" / POWER(10, {}))", scale));
35500                }
35501                self.write(" * INTERVAL '1 SECOND')");
35502            }
35503            _ => {
35504                // Default: TO_TIMESTAMP(value[, scale])
35505                self.write_keyword("TO_TIMESTAMP");
35506                self.write("(");
35507                self.generate_expression(&e.this)?;
35508                if let Some(s) = e.scale {
35509                    self.write(", ");
35510                    self.write(&s.to_string());
35511                }
35512                self.write(")");
35513            }
35514        }
35515        Ok(())
35516    }
35517
35518    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35519        // NAME col VALUE col1, col2, ...
35520        if !matches!(&*e.this, Expression::Null(_)) {
35521            self.write_keyword("NAME");
35522            self.write_space();
35523            self.generate_expression(&e.this)?;
35524        }
35525        if !e.expressions.is_empty() {
35526            self.write_space();
35527            self.write_keyword("VALUE");
35528            self.write_space();
35529            for (i, expr) in e.expressions.iter().enumerate() {
35530                if i > 0 {
35531                    self.write(", ");
35532                }
35533                self.generate_expression(expr)?;
35534            }
35535        }
35536        Ok(())
35537    }
35538
35539    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35540        // this(expressions) or (this)(expressions)
35541        if e.wrapped.is_some() {
35542            self.write("(");
35543        }
35544        self.generate_expression(&e.this)?;
35545        if e.wrapped.is_some() {
35546            self.write(")");
35547        }
35548        self.write("(");
35549        for (i, expr) in e.expressions.iter().enumerate() {
35550            if i > 0 {
35551                self.write(", ");
35552            }
35553            self.generate_expression(expr)?;
35554        }
35555        self.write(")");
35556        Ok(())
35557    }
35558
35559    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35560        // USING TEMPLATE this
35561        self.write_keyword("USING TEMPLATE");
35562        self.write_space();
35563        self.generate_expression(&e.this)?;
35564        Ok(())
35565    }
35566
35567    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35568        // UTC_TIME
35569        self.write_keyword("UTC_TIME");
35570        Ok(())
35571    }
35572
35573    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35574        // UTC_TIMESTAMP
35575        self.write_keyword("UTC_TIMESTAMP");
35576        Ok(())
35577    }
35578
35579    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35580        use crate::dialects::DialectType;
35581        // Choose UUID function name based on target dialect
35582        let func_name = match self.config.dialect {
35583            Some(DialectType::Snowflake) => "UUID_STRING",
35584            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35585            Some(DialectType::BigQuery) => "GENERATE_UUID",
35586            _ => {
35587                if let Some(name) = &e.name {
35588                    name.as_str()
35589                } else {
35590                    "UUID"
35591                }
35592            }
35593        };
35594        self.write_keyword(func_name);
35595        self.write("(");
35596        if let Some(this) = &e.this {
35597            self.generate_expression(this)?;
35598        }
35599        self.write(")");
35600        Ok(())
35601    }
35602
35603    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35604        // MAP(key1, value1, key2, value2, ...)
35605        self.write_keyword("MAP");
35606        self.write("(");
35607        let mut first = true;
35608        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35609            if !first {
35610                self.write(", ");
35611            }
35612            self.generate_expression(k)?;
35613            self.write(", ");
35614            self.generate_expression(v)?;
35615            first = false;
35616        }
35617        self.write(")");
35618        Ok(())
35619    }
35620
35621    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35622        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35623        self.write_keyword("VECTOR_SEARCH");
35624        self.write("(");
35625        self.generate_expression(&e.this)?;
35626        if let Some(col) = &e.column_to_search {
35627            self.write(", ");
35628            self.generate_expression(col)?;
35629        }
35630        if let Some(query_table) = &e.query_table {
35631            self.write(", ");
35632            self.generate_expression(query_table)?;
35633        }
35634        if let Some(query_col) = &e.query_column_to_search {
35635            self.write(", ");
35636            self.generate_expression(query_col)?;
35637        }
35638        if let Some(top_k) = &e.top_k {
35639            self.write(", ");
35640            self.generate_expression(top_k)?;
35641        }
35642        if let Some(dist_type) = &e.distance_type {
35643            self.write(", ");
35644            self.generate_expression(dist_type)?;
35645        }
35646        self.write(")");
35647        Ok(())
35648    }
35649
35650    fn generate_version(&mut self, e: &Version) -> Result<()> {
35651        // Python: f"FOR {expression.name} {kind} {expr}"
35652        // e.this = Identifier("TIMESTAMP" or "VERSION")
35653        // e.kind = "AS OF" (or "BETWEEN", etc.)
35654        // e.expression = the value expression
35655        // Hive does NOT use the FOR prefix for time travel
35656        use crate::dialects::DialectType;
35657        let skip_for = matches!(
35658            self.config.dialect,
35659            Some(DialectType::Hive) | Some(DialectType::Spark)
35660        );
35661        if !skip_for {
35662            self.write_keyword("FOR");
35663            self.write_space();
35664        }
35665        // Extract the name from this (which is an Identifier expression)
35666        match e.this.as_ref() {
35667            Expression::Identifier(ident) => {
35668                self.write_keyword(&ident.name);
35669            }
35670            _ => {
35671                self.generate_expression(&e.this)?;
35672            }
35673        }
35674        self.write_space();
35675        self.write_keyword(&e.kind);
35676        if let Some(expression) = &e.expression {
35677            self.write_space();
35678            self.generate_expression(expression)?;
35679        }
35680        Ok(())
35681    }
35682
35683    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35684        // Python: return self.sql(expression, "this")
35685        self.generate_expression(&e.this)?;
35686        Ok(())
35687    }
35688
35689    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35690        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35691        if e.this.is_some() {
35692            self.write_keyword("NOT VOLATILE");
35693        } else {
35694            self.write_keyword("VOLATILE");
35695        }
35696        Ok(())
35697    }
35698
35699    fn generate_watermark_column_constraint(
35700        &mut self,
35701        e: &WatermarkColumnConstraint,
35702    ) -> Result<()> {
35703        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35704        self.write_keyword("WATERMARK FOR");
35705        self.write_space();
35706        self.generate_expression(&e.this)?;
35707        self.write_space();
35708        self.write_keyword("AS");
35709        self.write_space();
35710        self.generate_expression(&e.expression)?;
35711        Ok(())
35712    }
35713
35714    fn generate_week(&mut self, e: &Week) -> Result<()> {
35715        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35716        self.write_keyword("WEEK");
35717        self.write("(");
35718        self.generate_expression(&e.this)?;
35719        if let Some(mode) = &e.mode {
35720            self.write(", ");
35721            self.generate_expression(mode)?;
35722        }
35723        self.write(")");
35724        Ok(())
35725    }
35726
35727    fn generate_when(&mut self, e: &When) -> Result<()> {
35728        // Python: WHEN {matched}{source}{condition} THEN {then}
35729        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35730        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35731        self.write_keyword("WHEN");
35732        self.write_space();
35733
35734        // Check if matched
35735        if let Some(matched) = &e.matched {
35736            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35737            match matched.as_ref() {
35738                Expression::Boolean(b) if b.value => {
35739                    self.write_keyword("MATCHED");
35740                }
35741                _ => {
35742                    self.write_keyword("NOT MATCHED");
35743                }
35744            }
35745        } else {
35746            self.write_keyword("NOT MATCHED");
35747        }
35748
35749        // BY SOURCE / BY TARGET
35750        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35751        // BY TARGET is the default and typically omitted in output
35752        // Only emit if the dialect supports BY SOURCE syntax
35753        if self.config.matched_by_source {
35754            if let Some(source) = &e.source {
35755                if let Expression::Boolean(b) = source.as_ref() {
35756                    if b.value {
35757                        // BY SOURCE
35758                        self.write_space();
35759                        self.write_keyword("BY SOURCE");
35760                    }
35761                    // BY TARGET (b.value == false) is omitted as it's the default
35762                } else {
35763                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35764                    self.write_space();
35765                    self.write_keyword("BY SOURCE");
35766                }
35767            }
35768        }
35769
35770        // Condition
35771        if let Some(condition) = &e.condition {
35772            self.write_space();
35773            self.write_keyword("AND");
35774            self.write_space();
35775            self.generate_expression(condition)?;
35776        }
35777
35778        self.write_space();
35779        self.write_keyword("THEN");
35780        self.write_space();
35781
35782        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35783        // MERGE actions are stored as Tuples with the action keyword as first element
35784        self.generate_merge_action(&e.then)?;
35785
35786        Ok(())
35787    }
35788
35789    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35790        match action {
35791            Expression::Tuple(tuple) => {
35792                let elements = &tuple.expressions;
35793                if elements.is_empty() {
35794                    return self.generate_expression(action);
35795                }
35796                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35797                match &elements[0] {
35798                    Expression::Var(v) if v.this == "INSERT" => {
35799                        self.write_keyword("INSERT");
35800                        // Spark: INSERT * (insert all columns)
35801                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35802                            self.write(" *");
35803                        } else {
35804                            let mut values_idx = 1;
35805                            // Check if second element is column list (Tuple)
35806                            if elements.len() > 1 {
35807                                if let Expression::Tuple(cols) = &elements[1] {
35808                                    // Could be columns or values - if there's a third element, second is columns
35809                                    if elements.len() > 2 {
35810                                        // Second is columns, third is values
35811                                        self.write(" (");
35812                                        for (i, col) in cols.expressions.iter().enumerate() {
35813                                            if i > 0 {
35814                                                self.write(", ");
35815                                            }
35816                                            // Strip MERGE target qualifiers from INSERT column list
35817                                            if !self.merge_strip_qualifiers.is_empty() {
35818                                                let stripped = self.strip_merge_qualifier(col);
35819                                                self.generate_expression(&stripped)?;
35820                                            } else {
35821                                                self.generate_expression(col)?;
35822                                            }
35823                                        }
35824                                        self.write(")");
35825                                        values_idx = 2;
35826                                    } else {
35827                                        // Only two elements: INSERT + values (no explicit columns)
35828                                        values_idx = 1;
35829                                    }
35830                                }
35831                            }
35832                            // Generate VALUES clause
35833                            if values_idx < elements.len() {
35834                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35835                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35836                                if !is_row {
35837                                    self.write_space();
35838                                    self.write_keyword("VALUES");
35839                                }
35840                                self.write(" ");
35841                                if let Expression::Tuple(vals) = &elements[values_idx] {
35842                                    self.write("(");
35843                                    for (i, val) in vals.expressions.iter().enumerate() {
35844                                        if i > 0 {
35845                                            self.write(", ");
35846                                        }
35847                                        self.generate_expression(val)?;
35848                                    }
35849                                    self.write(")");
35850                                } else {
35851                                    self.generate_expression(&elements[values_idx])?;
35852                                }
35853                            }
35854                        } // close else for INSERT * check
35855                    }
35856                    Expression::Var(v) if v.this == "UPDATE" => {
35857                        self.write_keyword("UPDATE");
35858                        // Spark: UPDATE * (update all columns)
35859                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35860                            self.write(" *");
35861                        } else if elements.len() > 1 {
35862                            self.write_space();
35863                            self.write_keyword("SET");
35864                            // In pretty mode, put assignments on next line with extra indent
35865                            if self.config.pretty {
35866                                self.write_newline();
35867                                self.indent_level += 1;
35868                                self.write_indent();
35869                            } else {
35870                                self.write_space();
35871                            }
35872                            if let Expression::Tuple(assignments) = &elements[1] {
35873                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35874                                    if i > 0 {
35875                                        if self.config.pretty {
35876                                            self.write(",");
35877                                            self.write_newline();
35878                                            self.write_indent();
35879                                        } else {
35880                                            self.write(", ");
35881                                        }
35882                                    }
35883                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35884                                    if !self.merge_strip_qualifiers.is_empty() {
35885                                        self.generate_merge_set_assignment(assignment)?;
35886                                    } else {
35887                                        self.generate_expression(assignment)?;
35888                                    }
35889                                }
35890                            } else {
35891                                self.generate_expression(&elements[1])?;
35892                            }
35893                            if self.config.pretty {
35894                                self.indent_level -= 1;
35895                            }
35896                        }
35897                    }
35898                    _ => {
35899                        // Fallback: generic tuple generation
35900                        self.generate_expression(action)?;
35901                    }
35902                }
35903            }
35904            Expression::Var(v)
35905                if v.this == "INSERT"
35906                    || v.this == "UPDATE"
35907                    || v.this == "DELETE"
35908                    || v.this == "DO NOTHING" =>
35909            {
35910                self.write_keyword(&v.this);
35911            }
35912            _ => {
35913                self.generate_expression(action)?;
35914            }
35915        }
35916        Ok(())
35917    }
35918
35919    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35920    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35921        match assignment {
35922            Expression::Eq(eq) => {
35923                // Strip qualifier from the left side if it matches a MERGE target name
35924                let stripped_left = self.strip_merge_qualifier(&eq.left);
35925                self.generate_expression(&stripped_left)?;
35926                self.write(" = ");
35927                self.generate_expression(&eq.right)?;
35928                Ok(())
35929            }
35930            other => self.generate_expression(other),
35931        }
35932    }
35933
35934    /// Strip table qualifier from a column reference if it matches a MERGE target name
35935    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35936        match expr {
35937            Expression::Column(col) => {
35938                if let Some(ref table_ident) = col.table {
35939                    if self
35940                        .merge_strip_qualifiers
35941                        .iter()
35942                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35943                    {
35944                        // Strip the table qualifier
35945                        let mut col = col.clone();
35946                        col.table = None;
35947                        return Expression::Column(col);
35948                    }
35949                }
35950                expr.clone()
35951            }
35952            Expression::Dot(dot) => {
35953                // table.column -> column (strip qualifier)
35954                if let Expression::Identifier(id) = &dot.this {
35955                    if self
35956                        .merge_strip_qualifiers
35957                        .iter()
35958                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35959                    {
35960                        return Expression::Identifier(dot.field.clone());
35961                    }
35962                }
35963                expr.clone()
35964            }
35965            _ => expr.clone(),
35966        }
35967    }
35968
35969    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35970        // Python: return self.expressions(expression, sep=" ", indent=False)
35971        for (i, expr) in e.expressions.iter().enumerate() {
35972            if i > 0 {
35973                // In pretty mode, each WHEN clause on its own line
35974                if self.config.pretty {
35975                    self.write_newline();
35976                    self.write_indent();
35977                } else {
35978                    self.write_space();
35979                }
35980            }
35981            self.generate_expression(expr)?;
35982        }
35983        Ok(())
35984    }
35985
35986    fn generate_where(&mut self, e: &Where) -> Result<()> {
35987        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35988        self.write_keyword("WHERE");
35989        self.write_space();
35990        self.generate_expression(&e.this)?;
35991        Ok(())
35992    }
35993
35994    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
35995        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
35996        self.write_keyword("WIDTH_BUCKET");
35997        self.write("(");
35998        self.generate_expression(&e.this)?;
35999        if let Some(min_value) = &e.min_value {
36000            self.write(", ");
36001            self.generate_expression(min_value)?;
36002        }
36003        if let Some(max_value) = &e.max_value {
36004            self.write(", ");
36005            self.generate_expression(max_value)?;
36006        }
36007        if let Some(num_buckets) = &e.num_buckets {
36008            self.write(", ");
36009            self.generate_expression(num_buckets)?;
36010        }
36011        self.write(")");
36012        Ok(())
36013    }
36014
36015    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
36016        // Window specification: PARTITION BY ... ORDER BY ... frame
36017        self.generate_window_spec(e)
36018    }
36019
36020    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
36021        // Window specification: PARTITION BY ... ORDER BY ... frame
36022        let mut has_content = false;
36023
36024        // PARTITION BY
36025        if !e.partition_by.is_empty() {
36026            self.write_keyword("PARTITION BY");
36027            self.write_space();
36028            for (i, expr) in e.partition_by.iter().enumerate() {
36029                if i > 0 {
36030                    self.write(", ");
36031                }
36032                self.generate_expression(expr)?;
36033            }
36034            has_content = true;
36035        }
36036
36037        // ORDER BY
36038        if !e.order_by.is_empty() {
36039            if has_content {
36040                self.write_space();
36041            }
36042            self.write_keyword("ORDER BY");
36043            self.write_space();
36044            for (i, ordered) in e.order_by.iter().enumerate() {
36045                if i > 0 {
36046                    self.write(", ");
36047                }
36048                self.generate_expression(&ordered.this)?;
36049                if ordered.desc {
36050                    self.write_space();
36051                    self.write_keyword("DESC");
36052                } else if ordered.explicit_asc {
36053                    self.write_space();
36054                    self.write_keyword("ASC");
36055                }
36056                if let Some(nulls_first) = ordered.nulls_first {
36057                    self.write_space();
36058                    self.write_keyword("NULLS");
36059                    self.write_space();
36060                    if nulls_first {
36061                        self.write_keyword("FIRST");
36062                    } else {
36063                        self.write_keyword("LAST");
36064                    }
36065                }
36066            }
36067            has_content = true;
36068        }
36069
36070        // Frame specification
36071        if let Some(frame) = &e.frame {
36072            if has_content {
36073                self.write_space();
36074            }
36075            self.generate_window_frame(frame)?;
36076        }
36077
36078        Ok(())
36079    }
36080
36081    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
36082        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
36083        self.write_keyword("WITH");
36084        self.write_space();
36085        if e.no.is_some() {
36086            self.write_keyword("NO");
36087            self.write_space();
36088        }
36089        self.write_keyword("DATA");
36090
36091        // statistics
36092        if let Some(statistics) = &e.statistics {
36093            self.write_space();
36094            self.write_keyword("AND");
36095            self.write_space();
36096            // Check if statistics is true or false
36097            match statistics.as_ref() {
36098                Expression::Boolean(b) if !b.value => {
36099                    self.write_keyword("NO");
36100                    self.write_space();
36101                }
36102                _ => {}
36103            }
36104            self.write_keyword("STATISTICS");
36105        }
36106        Ok(())
36107    }
36108
36109    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
36110        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
36111        self.write_keyword("WITH FILL");
36112
36113        if let Some(from_) = &e.from_ {
36114            self.write_space();
36115            self.write_keyword("FROM");
36116            self.write_space();
36117            self.generate_expression(from_)?;
36118        }
36119
36120        if let Some(to) = &e.to {
36121            self.write_space();
36122            self.write_keyword("TO");
36123            self.write_space();
36124            self.generate_expression(to)?;
36125        }
36126
36127        if let Some(step) = &e.step {
36128            self.write_space();
36129            self.write_keyword("STEP");
36130            self.write_space();
36131            self.generate_expression(step)?;
36132        }
36133
36134        if let Some(staleness) = &e.staleness {
36135            self.write_space();
36136            self.write_keyword("STALENESS");
36137            self.write_space();
36138            self.generate_expression(staleness)?;
36139        }
36140
36141        if let Some(interpolate) = &e.interpolate {
36142            self.write_space();
36143            self.write_keyword("INTERPOLATE");
36144            self.write(" (");
36145            // INTERPOLATE items use reversed alias format: name AS expression
36146            self.generate_interpolate_item(interpolate)?;
36147            self.write(")");
36148        }
36149
36150        Ok(())
36151    }
36152
36153    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
36154    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
36155        match expr {
36156            Expression::Alias(alias) => {
36157                // Output as: alias_name AS expression
36158                self.generate_identifier(&alias.alias)?;
36159                self.write_space();
36160                self.write_keyword("AS");
36161                self.write_space();
36162                self.generate_expression(&alias.this)?;
36163            }
36164            Expression::Tuple(tuple) => {
36165                for (i, item) in tuple.expressions.iter().enumerate() {
36166                    if i > 0 {
36167                        self.write(", ");
36168                    }
36169                    self.generate_interpolate_item(item)?;
36170                }
36171            }
36172            other => {
36173                self.generate_expression(other)?;
36174            }
36175        }
36176        Ok(())
36177    }
36178
36179    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
36180        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
36181        self.write_keyword("WITH JOURNAL TABLE");
36182        self.write("=");
36183        self.generate_expression(&e.this)?;
36184        Ok(())
36185    }
36186
36187    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
36188        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
36189        self.generate_expression(&e.this)?;
36190        self.write_space();
36191        self.write_keyword("WITH");
36192        self.write_space();
36193        self.write_keyword(&e.op);
36194        Ok(())
36195    }
36196
36197    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
36198        // Python: return f"WITH {self.expressions(expression, flat=True)}"
36199        self.write_keyword("WITH");
36200        self.write_space();
36201        for (i, expr) in e.expressions.iter().enumerate() {
36202            if i > 0 {
36203                self.write(", ");
36204            }
36205            self.generate_expression(expr)?;
36206        }
36207        Ok(())
36208    }
36209
36210    fn generate_with_schema_binding_property(
36211        &mut self,
36212        e: &WithSchemaBindingProperty,
36213    ) -> Result<()> {
36214        // Python: return f"WITH {self.sql(expression, 'this')}"
36215        self.write_keyword("WITH");
36216        self.write_space();
36217        self.generate_expression(&e.this)?;
36218        Ok(())
36219    }
36220
36221    fn generate_with_system_versioning_property(
36222        &mut self,
36223        e: &WithSystemVersioningProperty,
36224    ) -> Result<()> {
36225        // Python: complex logic for SYSTEM_VERSIONING with options
36226        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36227        // or SYSTEM_VERSIONING=ON/OFF
36228        // with WITH(...) wrapper if with_ is set
36229
36230        let mut parts = Vec::new();
36231
36232        if let Some(this) = &e.this {
36233            // HISTORY_TABLE=...
36234            let mut s = String::from("HISTORY_TABLE=");
36235            let mut gen = Generator::new();
36236            gen.generate_expression(this)?;
36237            s.push_str(&gen.output);
36238            parts.push(s);
36239        }
36240
36241        if let Some(data_consistency) = &e.data_consistency {
36242            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36243            let mut gen = Generator::new();
36244            gen.generate_expression(data_consistency)?;
36245            s.push_str(&gen.output);
36246            parts.push(s);
36247        }
36248
36249        if let Some(retention_period) = &e.retention_period {
36250            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36251            let mut gen = Generator::new();
36252            gen.generate_expression(retention_period)?;
36253            s.push_str(&gen.output);
36254            parts.push(s);
36255        }
36256
36257        self.write_keyword("SYSTEM_VERSIONING");
36258        self.write("=");
36259
36260        if !parts.is_empty() {
36261            self.write_keyword("ON");
36262            self.write("(");
36263            self.write(&parts.join(", "));
36264            self.write(")");
36265        } else if e.on.is_some() {
36266            self.write_keyword("ON");
36267        } else {
36268            self.write_keyword("OFF");
36269        }
36270
36271        // Wrap in WITH(...) if with_ is set
36272        if e.with_.is_some() {
36273            let inner = self.output.clone();
36274            self.output.clear();
36275            self.write("WITH(");
36276            self.write(&inner);
36277            self.write(")");
36278        }
36279
36280        Ok(())
36281    }
36282
36283    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36284        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36285        self.write_keyword("WITH");
36286        self.write(" (");
36287        for (i, expr) in e.expressions.iter().enumerate() {
36288            if i > 0 {
36289                self.write(", ");
36290            }
36291            self.generate_expression(expr)?;
36292        }
36293        self.write(")");
36294        Ok(())
36295    }
36296
36297    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36298        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36299        // return self.func("XMLELEMENT", name, *expression.expressions)
36300        self.write_keyword("XMLELEMENT");
36301        self.write("(");
36302
36303        if e.evalname.is_some() {
36304            self.write_keyword("EVALNAME");
36305        } else {
36306            self.write_keyword("NAME");
36307        }
36308        self.write_space();
36309        self.generate_expression(&e.this)?;
36310
36311        for expr in &e.expressions {
36312            self.write(", ");
36313            self.generate_expression(expr)?;
36314        }
36315        self.write(")");
36316        Ok(())
36317    }
36318
36319    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36320        // XMLGET(this, expression [, instance])
36321        self.write_keyword("XMLGET");
36322        self.write("(");
36323        self.generate_expression(&e.this)?;
36324        self.write(", ");
36325        self.generate_expression(&e.expression)?;
36326        if let Some(instance) = &e.instance {
36327            self.write(", ");
36328            self.generate_expression(instance)?;
36329        }
36330        self.write(")");
36331        Ok(())
36332    }
36333
36334    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36335        // Python: this + optional (expr)
36336        self.generate_expression(&e.this)?;
36337        if let Some(expression) = &e.expression {
36338            self.write("(");
36339            self.generate_expression(expression)?;
36340            self.write(")");
36341        }
36342        Ok(())
36343    }
36344
36345    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36346        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36347        self.write_keyword("XMLTABLE");
36348        self.write("(");
36349
36350        if self.config.pretty {
36351            self.indent_level += 1;
36352            self.write_newline();
36353            self.write_indent();
36354            self.generate_expression(&e.this)?;
36355
36356            if let Some(passing) = &e.passing {
36357                self.write_newline();
36358                self.write_indent();
36359                self.write_keyword("PASSING");
36360                if let Expression::Tuple(tuple) = passing.as_ref() {
36361                    for expr in &tuple.expressions {
36362                        self.write_newline();
36363                        self.indent_level += 1;
36364                        self.write_indent();
36365                        self.generate_expression(expr)?;
36366                        self.indent_level -= 1;
36367                    }
36368                } else {
36369                    self.write_newline();
36370                    self.indent_level += 1;
36371                    self.write_indent();
36372                    self.generate_expression(passing)?;
36373                    self.indent_level -= 1;
36374                }
36375            }
36376
36377            if e.by_ref.is_some() {
36378                self.write_newline();
36379                self.write_indent();
36380                self.write_keyword("RETURNING SEQUENCE BY REF");
36381            }
36382
36383            if !e.columns.is_empty() {
36384                self.write_newline();
36385                self.write_indent();
36386                self.write_keyword("COLUMNS");
36387                for (i, col) in e.columns.iter().enumerate() {
36388                    self.write_newline();
36389                    self.indent_level += 1;
36390                    self.write_indent();
36391                    self.generate_expression(col)?;
36392                    self.indent_level -= 1;
36393                    if i < e.columns.len() - 1 {
36394                        self.write(",");
36395                    }
36396                }
36397            }
36398
36399            self.indent_level -= 1;
36400            self.write_newline();
36401            self.write_indent();
36402            self.write(")");
36403            return Ok(());
36404        }
36405
36406        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36407        if let Some(namespaces) = &e.namespaces {
36408            self.write_keyword("XMLNAMESPACES");
36409            self.write("(");
36410            // Unwrap Tuple if present to avoid extra parentheses
36411            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36412                for (i, expr) in tuple.expressions.iter().enumerate() {
36413                    if i > 0 {
36414                        self.write(", ");
36415                    }
36416                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36417                    // See xmlnamespace_sql in generator.py
36418                    if !matches!(expr, Expression::Alias(_)) {
36419                        self.write_keyword("DEFAULT");
36420                        self.write_space();
36421                    }
36422                    self.generate_expression(expr)?;
36423                }
36424            } else {
36425                // Single namespace - check if DEFAULT
36426                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36427                    self.write_keyword("DEFAULT");
36428                    self.write_space();
36429                }
36430                self.generate_expression(namespaces)?;
36431            }
36432            self.write("), ");
36433        }
36434
36435        // XPath expression
36436        self.generate_expression(&e.this)?;
36437
36438        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36439        if let Some(passing) = &e.passing {
36440            self.write_space();
36441            self.write_keyword("PASSING");
36442            self.write_space();
36443            // Unwrap Tuple if present to avoid extra parentheses
36444            if let Expression::Tuple(tuple) = passing.as_ref() {
36445                for (i, expr) in tuple.expressions.iter().enumerate() {
36446                    if i > 0 {
36447                        self.write(", ");
36448                    }
36449                    self.generate_expression(expr)?;
36450                }
36451            } else {
36452                self.generate_expression(passing)?;
36453            }
36454        }
36455
36456        // RETURNING SEQUENCE BY REF
36457        if e.by_ref.is_some() {
36458            self.write_space();
36459            self.write_keyword("RETURNING SEQUENCE BY REF");
36460        }
36461
36462        // COLUMNS clause
36463        if !e.columns.is_empty() {
36464            self.write_space();
36465            self.write_keyword("COLUMNS");
36466            self.write_space();
36467            for (i, col) in e.columns.iter().enumerate() {
36468                if i > 0 {
36469                    self.write(", ");
36470                }
36471                self.generate_expression(col)?;
36472            }
36473        }
36474
36475        self.write(")");
36476        Ok(())
36477    }
36478
36479    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36480        // Python: return self.connector_sql(expression, "XOR", stack)
36481        // Handles: this XOR expression or expressions joined by XOR
36482        if let Some(this) = &e.this {
36483            self.generate_expression(this)?;
36484            if let Some(expression) = &e.expression {
36485                self.write_space();
36486                self.write_keyword("XOR");
36487                self.write_space();
36488                self.generate_expression(expression)?;
36489            }
36490        }
36491
36492        // Handle multiple expressions
36493        for (i, expr) in e.expressions.iter().enumerate() {
36494            if i > 0 || e.this.is_some() {
36495                self.write_space();
36496                self.write_keyword("XOR");
36497                self.write_space();
36498            }
36499            self.generate_expression(expr)?;
36500        }
36501        Ok(())
36502    }
36503
36504    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36505        // ZIPF(this, elementcount [, gen])
36506        self.write_keyword("ZIPF");
36507        self.write("(");
36508        self.generate_expression(&e.this)?;
36509        if let Some(elementcount) = &e.elementcount {
36510            self.write(", ");
36511            self.generate_expression(elementcount)?;
36512        }
36513        if let Some(gen) = &e.gen {
36514            self.write(", ");
36515            self.generate_expression(gen)?;
36516        }
36517        self.write(")");
36518        Ok(())
36519    }
36520}
36521
36522impl Default for Generator {
36523    fn default() -> Self {
36524        Self::new()
36525    }
36526}
36527
36528#[cfg(test)]
36529mod tests {
36530    use super::*;
36531    use crate::parser::Parser;
36532
36533    fn roundtrip(sql: &str) -> String {
36534        let ast = Parser::parse_sql(sql).unwrap();
36535        Generator::sql(&ast[0]).unwrap()
36536    }
36537
36538    #[test]
36539    fn test_simple_select() {
36540        let result = roundtrip("SELECT 1");
36541        assert_eq!(result, "SELECT 1");
36542    }
36543
36544    #[test]
36545    fn test_select_from() {
36546        let result = roundtrip("SELECT a, b FROM t");
36547        assert_eq!(result, "SELECT a, b FROM t");
36548    }
36549
36550    #[test]
36551    fn test_select_where() {
36552        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36553        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36554    }
36555
36556    #[test]
36557    fn test_select_join() {
36558        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36559        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36560    }
36561
36562    #[test]
36563    fn test_insert() {
36564        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36565        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36566    }
36567
36568    #[test]
36569    fn test_pretty_print() {
36570        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36571        let result = Generator::pretty_sql(&ast[0]).unwrap();
36572        assert!(result.contains('\n'));
36573    }
36574
36575    #[test]
36576    fn test_window_function() {
36577        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36578        assert_eq!(
36579            result,
36580            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36581        );
36582    }
36583
36584    #[test]
36585    fn test_window_function_with_frame() {
36586        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36587        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36588    }
36589
36590    #[test]
36591    fn test_aggregate_with_filter() {
36592        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36593        assert_eq!(
36594            result,
36595            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36596        );
36597    }
36598
36599    #[test]
36600    fn test_subscript() {
36601        let result = roundtrip("SELECT arr[0]");
36602        assert_eq!(result, "SELECT arr[0]");
36603    }
36604
36605    // DDL tests
36606    #[test]
36607    fn test_create_table() {
36608        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36609        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36610    }
36611
36612    #[test]
36613    fn test_create_table_with_constraints() {
36614        let result = roundtrip(
36615            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36616        );
36617        assert_eq!(
36618            result,
36619            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36620        );
36621    }
36622
36623    #[test]
36624    fn test_create_table_if_not_exists() {
36625        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36626        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36627    }
36628
36629    #[test]
36630    fn test_drop_table() {
36631        let result = roundtrip("DROP TABLE users");
36632        assert_eq!(result, "DROP TABLE users");
36633    }
36634
36635    #[test]
36636    fn test_drop_table_if_exists_cascade() {
36637        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36638        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36639    }
36640
36641    #[test]
36642    fn test_alter_table_add_column() {
36643        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36644        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36645    }
36646
36647    #[test]
36648    fn test_alter_table_drop_column() {
36649        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36650        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36651    }
36652
36653    #[test]
36654    fn test_create_index() {
36655        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36656        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36657    }
36658
36659    #[test]
36660    fn test_create_unique_index() {
36661        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36662        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36663    }
36664
36665    #[test]
36666    fn test_drop_index() {
36667        let result = roundtrip("DROP INDEX idx_name");
36668        assert_eq!(result, "DROP INDEX idx_name");
36669    }
36670
36671    #[test]
36672    fn test_create_view() {
36673        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36674        assert_eq!(
36675            result,
36676            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36677        );
36678    }
36679
36680    #[test]
36681    fn test_drop_view() {
36682        let result = roundtrip("DROP VIEW active_users");
36683        assert_eq!(result, "DROP VIEW active_users");
36684    }
36685
36686    #[test]
36687    fn test_truncate() {
36688        let result = roundtrip("TRUNCATE TABLE users");
36689        assert_eq!(result, "TRUNCATE TABLE users");
36690    }
36691
36692    #[test]
36693    fn test_string_literal_escaping_default() {
36694        // Default: double single quotes
36695        let result = roundtrip("SELECT 'hello'");
36696        assert_eq!(result, "SELECT 'hello'");
36697
36698        // Single quotes are doubled
36699        let result = roundtrip("SELECT 'it''s a test'");
36700        assert_eq!(result, "SELECT 'it''s a test'");
36701    }
36702
36703    #[test]
36704    fn test_not_in_style_prefix_default_generic() {
36705        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36706        assert_eq!(
36707            result,
36708            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36709        );
36710    }
36711
36712    #[test]
36713    fn test_not_in_style_infix_generic_override() {
36714        let ast =
36715            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36716                .unwrap();
36717        let config = GeneratorConfig {
36718            not_in_style: NotInStyle::Infix,
36719            ..Default::default()
36720        };
36721        let mut gen = Generator::with_config(config);
36722        let result = gen.generate(&ast[0]).unwrap();
36723        assert_eq!(
36724            result,
36725            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36726        );
36727    }
36728
36729    #[test]
36730    fn test_string_literal_escaping_mysql() {
36731        use crate::dialects::DialectType;
36732
36733        let config = GeneratorConfig {
36734            dialect: Some(DialectType::MySQL),
36735            ..Default::default()
36736        };
36737
36738        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36739        let mut gen = Generator::with_config(config.clone());
36740        let result = gen.generate(&ast[0]).unwrap();
36741        assert_eq!(result, "SELECT 'hello'");
36742
36743        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36744        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36745        let mut gen = Generator::with_config(config.clone());
36746        let result = gen.generate(&ast[0]).unwrap();
36747        assert_eq!(result, "SELECT 'it''s'");
36748    }
36749
36750    #[test]
36751    fn test_string_literal_escaping_postgres() {
36752        use crate::dialects::DialectType;
36753
36754        let config = GeneratorConfig {
36755            dialect: Some(DialectType::PostgreSQL),
36756            ..Default::default()
36757        };
36758
36759        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36760        let mut gen = Generator::with_config(config.clone());
36761        let result = gen.generate(&ast[0]).unwrap();
36762        assert_eq!(result, "SELECT 'hello'");
36763
36764        // PostgreSQL uses doubled quotes for regular strings
36765        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36766        let mut gen = Generator::with_config(config.clone());
36767        let result = gen.generate(&ast[0]).unwrap();
36768        assert_eq!(result, "SELECT 'it''s'");
36769    }
36770
36771    #[test]
36772    fn test_string_literal_escaping_bigquery() {
36773        use crate::dialects::DialectType;
36774
36775        let config = GeneratorConfig {
36776            dialect: Some(DialectType::BigQuery),
36777            ..Default::default()
36778        };
36779
36780        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36781        let mut gen = Generator::with_config(config.clone());
36782        let result = gen.generate(&ast[0]).unwrap();
36783        assert_eq!(result, "SELECT 'hello'");
36784
36785        // BigQuery escapes single quotes with backslash
36786        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36787        let mut gen = Generator::with_config(config.clone());
36788        let result = gen.generate(&ast[0]).unwrap();
36789        assert_eq!(result, "SELECT 'it\\'s'");
36790    }
36791
36792    #[test]
36793    fn test_generate_deep_and_chain_without_stack_growth() {
36794        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36795            Expression::column("c0"),
36796            Expression::number(0),
36797        )));
36798
36799        for i in 1..2500 {
36800            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36801                Expression::column(format!("c{i}")),
36802                Expression::number(i as i64),
36803            )));
36804            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
36805        }
36806
36807        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
36808        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36809    }
36810
36811    #[test]
36812    fn test_generate_deep_or_chain_without_stack_growth() {
36813        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36814            Expression::column("c0"),
36815            Expression::number(0),
36816        )));
36817
36818        for i in 1..2500 {
36819            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36820                Expression::column(format!("c{i}")),
36821                Expression::number(i as i64),
36822            )));
36823            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
36824        }
36825
36826        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
36827        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36828    }
36829}