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                inferred_type: None,
2173            })),
2174
2175            // Identifier: add alias from identifier name
2176            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2177                this: expr.clone(),
2178                alias: ident.clone(),
2179                column_aliases: Vec::new(),
2180                pre_alias_comments: Vec::new(),
2181                trailing_comments: Vec::new(),
2182                inferred_type: None,
2183            })),
2184
2185            // Subquery: recursively process and add alias if inner returns a named column
2186            Expression::Subquery(sq) => {
2187                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2188                // Subqueries that are already aliased keep their alias
2189                if sq.alias.is_some() {
2190                    processed
2191                } else {
2192                    // If there's no alias, keep it as-is (let TSQL handle it)
2193                    processed
2194                }
2195            }
2196
2197            // Star expressions (*) - don't alias
2198            Expression::Star(_) => expr,
2199
2200            // For other expressions, don't add an alias
2201            // (function calls, literals, etc. would need explicit aliases anyway)
2202            _ => expr,
2203        }
2204    }
2205
2206    /// Try to evaluate a constant arithmetic expression to a number literal.
2207    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2208    /// otherwise returns the original expression.
2209    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2210        match expr {
2211            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
2212            Expression::Add(op) => {
2213                let left = Self::try_evaluate_constant(&op.left)?;
2214                let right = Self::try_evaluate_constant(&op.right)?;
2215                Some(left + right)
2216            }
2217            Expression::Sub(op) => {
2218                let left = Self::try_evaluate_constant(&op.left)?;
2219                let right = Self::try_evaluate_constant(&op.right)?;
2220                Some(left - right)
2221            }
2222            Expression::Mul(op) => {
2223                let left = Self::try_evaluate_constant(&op.left)?;
2224                let right = Self::try_evaluate_constant(&op.right)?;
2225                Some(left * right)
2226            }
2227            Expression::Div(op) => {
2228                let left = Self::try_evaluate_constant(&op.left)?;
2229                let right = Self::try_evaluate_constant(&op.right)?;
2230                if right != 0 {
2231                    Some(left / right)
2232                } else {
2233                    None
2234                }
2235            }
2236            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2237            _ => None,
2238        }
2239    }
2240
2241    /// Check if an identifier is a reserved keyword for the current dialect
2242    fn is_reserved_keyword(&self, name: &str) -> bool {
2243        use crate::dialects::DialectType;
2244        let lower = name.to_lowercase();
2245        let lower_ref = lower.as_str();
2246
2247        match self.config.dialect {
2248            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2249            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2250                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2251            }
2252            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2253            Some(DialectType::SingleStore) => {
2254                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2255            }
2256            Some(DialectType::StarRocks) => {
2257                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2258            }
2259            Some(DialectType::PostgreSQL)
2260            | Some(DialectType::CockroachDB)
2261            | Some(DialectType::Materialize)
2262            | Some(DialectType::RisingWave) => {
2263                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2264            }
2265            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2266            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2267            // meaning it never quotes identifiers based on reserved word status.
2268            Some(DialectType::Snowflake) => false,
2269            // ClickHouse: don't quote reserved keywords to preserve identity output
2270            Some(DialectType::ClickHouse) => false,
2271            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2272            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2273            Some(DialectType::Teradata) => false,
2274            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2275            Some(DialectType::TSQL)
2276            | Some(DialectType::Fabric)
2277            | Some(DialectType::Oracle)
2278            | Some(DialectType::Spark)
2279            | Some(DialectType::Databricks)
2280            | Some(DialectType::Hive)
2281            | Some(DialectType::Solr) => false,
2282            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2283                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2284            }
2285            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2286            // For Generic dialect or None, don't add extra quoting to preserve identity
2287            Some(DialectType::Generic) | None => false,
2288            // For other dialects, use standard SQL reserved keywords
2289            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2290        }
2291    }
2292
2293    /// Normalize function name based on dialect settings
2294    fn normalize_func_name(&self, name: &str) -> String {
2295        match self.config.normalize_functions {
2296            NormalizeFunctions::Upper => name.to_uppercase(),
2297            NormalizeFunctions::Lower => name.to_lowercase(),
2298            NormalizeFunctions::None => name.to_string(),
2299        }
2300    }
2301
2302    /// Generate a SQL string from an AST expression.
2303    ///
2304    /// This is the primary generation method. It clears any previous internal state,
2305    /// walks the expression tree, and returns the resulting SQL text. The output
2306    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2307    ///
2308    /// The generator can be reused across multiple calls; each call to `generate`
2309    /// resets the internal buffer.
2310    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2311        self.output.clear();
2312        self.generate_expression(expr)?;
2313        Ok(std::mem::take(&mut self.output))
2314    }
2315
2316    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2317    ///
2318    /// This is a static helper that creates a throwaway `Generator` internally.
2319    /// For repeated generation, prefer constructing a `Generator` once and calling
2320    /// [`generate`](Self::generate) on it.
2321    pub fn sql(expr: &Expression) -> Result<String> {
2322        let mut gen = Generator::new();
2323        gen.generate(expr)
2324    }
2325
2326    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2327    ///
2328    /// Produces human-readable output with newlines and indentation. A trailing
2329    /// semicolon is appended automatically if not already present.
2330    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2331        let config = GeneratorConfig {
2332            pretty: true,
2333            ..Default::default()
2334        };
2335        let mut gen = Generator::with_config(config);
2336        let mut sql = gen.generate(expr)?;
2337        // Add semicolon for pretty output
2338        if !sql.ends_with(';') {
2339            sql.push(';');
2340        }
2341        Ok(sql)
2342    }
2343
2344    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2345        match expr {
2346            Expression::Select(select) => self.generate_select(select),
2347            Expression::Union(union) => self.generate_union(union),
2348            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2349            Expression::Except(except) => self.generate_except(except),
2350            Expression::Insert(insert) => self.generate_insert(insert),
2351            Expression::Update(update) => self.generate_update(update),
2352            Expression::Delete(delete) => self.generate_delete(delete),
2353            Expression::Literal(lit) => self.generate_literal(lit),
2354            Expression::Boolean(b) => self.generate_boolean(b),
2355            Expression::Null(_) => {
2356                self.write_keyword("NULL");
2357                Ok(())
2358            }
2359            Expression::Identifier(id) => self.generate_identifier(id),
2360            Expression::Column(col) => self.generate_column(col),
2361            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2362            Expression::Connect(c) => self.generate_connect_expr(c),
2363            Expression::Prior(p) => self.generate_prior(p),
2364            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2365            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2366            Expression::Table(table) => self.generate_table(table),
2367            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2368            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2369            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2370            Expression::Star(star) => self.generate_star(star),
2371            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2372            Expression::Alias(alias) => self.generate_alias(alias),
2373            Expression::Cast(cast) => self.generate_cast(cast),
2374            Expression::Collation(coll) => self.generate_collation(coll),
2375            Expression::Case(case) => self.generate_case(case),
2376            Expression::Function(func) => self.generate_function(func),
2377            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2378            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2379            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2380            Expression::Interval(interval) => self.generate_interval(interval),
2381
2382            // String functions
2383            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2384            Expression::Substring(f) => self.generate_substring(f),
2385            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2386            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2387            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2388            Expression::Trim(f) => self.generate_trim(f),
2389            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2390            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2391            Expression::Replace(f) => self.generate_replace(f),
2392            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2393            Expression::Left(f) => self.generate_left_right("LEFT", f),
2394            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2395            Expression::Repeat(f) => self.generate_repeat(f),
2396            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2397            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2398            Expression::Split(f) => self.generate_split(f),
2399            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2400            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2401            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2402            Expression::Overlay(f) => self.generate_overlay(f),
2403
2404            // Math functions
2405            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2406            Expression::Round(f) => self.generate_round(f),
2407            Expression::Floor(f) => self.generate_floor(f),
2408            Expression::Ceil(f) => self.generate_ceil(f),
2409            Expression::Power(f) => self.generate_power(f),
2410            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2411            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2412            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2413            Expression::Log(f) => self.generate_log(f),
2414            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2415            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2416            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2417            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2418
2419            // Date/time functions
2420            Expression::CurrentDate(_) => {
2421                self.write_keyword("CURRENT_DATE");
2422                Ok(())
2423            }
2424            Expression::CurrentTime(f) => self.generate_current_time(f),
2425            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2426            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2427            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2428            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2429            Expression::DateDiff(f) => self.generate_datediff(f),
2430            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2431            Expression::Extract(f) => self.generate_extract(f),
2432            Expression::ToDate(f) => self.generate_to_date(f),
2433            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2434
2435            // Control flow functions
2436            Expression::Coalesce(f) => {
2437                // Use original function name if preserved (COALESCE, IFNULL)
2438                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2439                self.generate_vararg_func(func_name, &f.expressions)
2440            }
2441            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2442            Expression::IfFunc(f) => self.generate_if_func(f),
2443            Expression::IfNull(f) => self.generate_ifnull(f),
2444            Expression::Nvl(f) => self.generate_nvl(f),
2445            Expression::Nvl2(f) => self.generate_nvl2(f),
2446
2447            // Type conversion
2448            Expression::TryCast(cast) => self.generate_try_cast(cast),
2449            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2450
2451            // Typed aggregate functions
2452            Expression::Count(f) => self.generate_count(f),
2453            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2454            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2455            Expression::Min(f) => self.generate_agg_func("MIN", f),
2456            Expression::Max(f) => self.generate_agg_func("MAX", f),
2457            Expression::GroupConcat(f) => self.generate_group_concat(f),
2458            Expression::StringAgg(f) => self.generate_string_agg(f),
2459            Expression::ListAgg(f) => self.generate_listagg(f),
2460            Expression::ArrayAgg(f) => {
2461                // Allow cross-dialect transforms to override the function name
2462                // (e.g., COLLECT_LIST for Spark)
2463                let override_name = f
2464                    .name
2465                    .as_ref()
2466                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
2467                    .map(|n| n.to_uppercase());
2468                match override_name {
2469                    Some(name) => self.generate_agg_func(&name, f),
2470                    None => self.generate_agg_func("ARRAY_AGG", f),
2471                }
2472            }
2473            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2474            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2475            Expression::SumIf(f) => self.generate_sum_if(f),
2476            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2477            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2478            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2479            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2480            Expression::VarPop(f) => {
2481                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2482                    "VARIANCE_POP"
2483                } else {
2484                    "VAR_POP"
2485                };
2486                self.generate_agg_func(name, f)
2487            }
2488            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2489            Expression::Skewness(f) => {
2490                let name = match self.config.dialect {
2491                    Some(DialectType::Snowflake) => "SKEW",
2492                    _ => "SKEWNESS",
2493                };
2494                self.generate_agg_func(name, f)
2495            }
2496            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2497            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2498            Expression::First(f) => self.generate_agg_func("FIRST", f),
2499            Expression::Last(f) => self.generate_agg_func("LAST", f),
2500            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2501            Expression::ApproxDistinct(f) => {
2502                match self.config.dialect {
2503                    Some(DialectType::Hive)
2504                    | Some(DialectType::Spark)
2505                    | Some(DialectType::Databricks)
2506                    | Some(DialectType::BigQuery) => {
2507                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2508                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2509                    }
2510                    Some(DialectType::Redshift) => {
2511                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2512                        self.write_keyword("APPROXIMATE COUNT");
2513                        self.write("(");
2514                        self.write_keyword("DISTINCT");
2515                        self.write(" ");
2516                        self.generate_expression(&f.this)?;
2517                        self.write(")");
2518                        Ok(())
2519                    }
2520                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2521                }
2522            }
2523            Expression::ApproxCountDistinct(f) => {
2524                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2525            }
2526            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2527            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2528            Expression::LogicalAnd(f) => {
2529                let name = match self.config.dialect {
2530                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2531                    Some(DialectType::Spark)
2532                    | Some(DialectType::Databricks)
2533                    | Some(DialectType::PostgreSQL)
2534                    | Some(DialectType::DuckDB)
2535                    | Some(DialectType::Redshift) => "BOOL_AND",
2536                    Some(DialectType::Oracle)
2537                    | Some(DialectType::SQLite)
2538                    | Some(DialectType::MySQL) => "MIN",
2539                    _ => "BOOL_AND",
2540                };
2541                self.generate_agg_func(name, f)
2542            }
2543            Expression::LogicalOr(f) => {
2544                let name = match self.config.dialect {
2545                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2546                    Some(DialectType::Spark)
2547                    | Some(DialectType::Databricks)
2548                    | Some(DialectType::PostgreSQL)
2549                    | Some(DialectType::DuckDB)
2550                    | Some(DialectType::Redshift) => "BOOL_OR",
2551                    Some(DialectType::Oracle)
2552                    | Some(DialectType::SQLite)
2553                    | Some(DialectType::MySQL) => "MAX",
2554                    _ => "BOOL_OR",
2555                };
2556                self.generate_agg_func(name, f)
2557            }
2558
2559            // Typed window functions
2560            Expression::RowNumber(_) => {
2561                if self.config.dialect == Some(DialectType::ClickHouse) {
2562                    self.write("row_number");
2563                } else {
2564                    self.write_keyword("ROW_NUMBER");
2565                }
2566                self.write("()");
2567                Ok(())
2568            }
2569            Expression::Rank(r) => {
2570                self.write_keyword("RANK");
2571                self.write("(");
2572                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2573                if !r.args.is_empty() {
2574                    for (i, arg) in r.args.iter().enumerate() {
2575                        if i > 0 {
2576                            self.write(", ");
2577                        }
2578                        self.generate_expression(arg)?;
2579                    }
2580                } else if let Some(order_by) = &r.order_by {
2581                    // DuckDB: RANK(ORDER BY col)
2582                    self.write_keyword(" ORDER BY ");
2583                    for (i, ob) in order_by.iter().enumerate() {
2584                        if i > 0 {
2585                            self.write(", ");
2586                        }
2587                        self.generate_ordered(ob)?;
2588                    }
2589                }
2590                self.write(")");
2591                Ok(())
2592            }
2593            Expression::DenseRank(dr) => {
2594                self.write_keyword("DENSE_RANK");
2595                self.write("(");
2596                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2597                for (i, arg) in dr.args.iter().enumerate() {
2598                    if i > 0 {
2599                        self.write(", ");
2600                    }
2601                    self.generate_expression(arg)?;
2602                }
2603                self.write(")");
2604                Ok(())
2605            }
2606            Expression::NTile(f) => self.generate_ntile(f),
2607            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2608            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2609            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
2610            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
2611            Expression::NthValue(f) => self.generate_nth_value(f),
2612            Expression::PercentRank(pr) => {
2613                self.write_keyword("PERCENT_RANK");
2614                self.write("(");
2615                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2616                if !pr.args.is_empty() {
2617                    for (i, arg) in pr.args.iter().enumerate() {
2618                        if i > 0 {
2619                            self.write(", ");
2620                        }
2621                        self.generate_expression(arg)?;
2622                    }
2623                } else if let Some(order_by) = &pr.order_by {
2624                    // DuckDB: PERCENT_RANK(ORDER BY col)
2625                    self.write_keyword(" ORDER BY ");
2626                    for (i, ob) in order_by.iter().enumerate() {
2627                        if i > 0 {
2628                            self.write(", ");
2629                        }
2630                        self.generate_ordered(ob)?;
2631                    }
2632                }
2633                self.write(")");
2634                Ok(())
2635            }
2636            Expression::CumeDist(cd) => {
2637                self.write_keyword("CUME_DIST");
2638                self.write("(");
2639                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2640                if !cd.args.is_empty() {
2641                    for (i, arg) in cd.args.iter().enumerate() {
2642                        if i > 0 {
2643                            self.write(", ");
2644                        }
2645                        self.generate_expression(arg)?;
2646                    }
2647                } else if let Some(order_by) = &cd.order_by {
2648                    // DuckDB: CUME_DIST(ORDER BY col)
2649                    self.write_keyword(" ORDER BY ");
2650                    for (i, ob) in order_by.iter().enumerate() {
2651                        if i > 0 {
2652                            self.write(", ");
2653                        }
2654                        self.generate_ordered(ob)?;
2655                    }
2656                }
2657                self.write(")");
2658                Ok(())
2659            }
2660            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2661            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2662
2663            // Additional string functions
2664            Expression::Contains(f) => {
2665                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2666            }
2667            Expression::StartsWith(f) => {
2668                let name = match self.config.dialect {
2669                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2670                    _ => "STARTS_WITH",
2671                };
2672                self.generate_binary_func(name, &f.this, &f.expression)
2673            }
2674            Expression::EndsWith(f) => {
2675                let name = match self.config.dialect {
2676                    Some(DialectType::Snowflake) => "ENDSWITH",
2677                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2678                    Some(DialectType::ClickHouse) => "endsWith",
2679                    _ => "ENDS_WITH",
2680                };
2681                self.generate_binary_func(name, &f.this, &f.expression)
2682            }
2683            Expression::Position(f) => self.generate_position(f),
2684            Expression::Initcap(f) => match self.config.dialect {
2685                Some(DialectType::Presto)
2686                | Some(DialectType::Trino)
2687                | Some(DialectType::Athena) => {
2688                    self.write_keyword("REGEXP_REPLACE");
2689                    self.write("(");
2690                    self.generate_expression(&f.this)?;
2691                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2692                    Ok(())
2693                }
2694                _ => self.generate_simple_func("INITCAP", &f.this),
2695            },
2696            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2697            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2698            Expression::CharFunc(f) => self.generate_char_func(f),
2699            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2700            Expression::Levenshtein(f) => {
2701                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2702            }
2703
2704            // Additional math functions
2705            Expression::ModFunc(f) => self.generate_mod_func(f),
2706            Expression::Random(_) => {
2707                self.write_keyword("RANDOM");
2708                self.write("()");
2709                Ok(())
2710            }
2711            Expression::Rand(f) => self.generate_rand(f),
2712            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2713            Expression::Pi(_) => {
2714                self.write_keyword("PI");
2715                self.write("()");
2716                Ok(())
2717            }
2718            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2719            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2720            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2721            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2722            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2723            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2724            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2725            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2726            Expression::Atan2(f) => {
2727                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2728                self.generate_binary_func(name, &f.this, &f.expression)
2729            }
2730
2731            // Control flow
2732            Expression::Decode(f) => self.generate_decode(f),
2733
2734            // Additional date/time functions
2735            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2736            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2737            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2738            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2739            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2740            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2741            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2742            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2743            Expression::DayOfWeek(f) => {
2744                let name = match self.config.dialect {
2745                    Some(DialectType::Presto)
2746                    | Some(DialectType::Trino)
2747                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2748                    Some(DialectType::DuckDB) => "ISODOW",
2749                    _ => "DAYOFWEEK",
2750                };
2751                self.generate_simple_func(name, &f.this)
2752            }
2753            Expression::DayOfMonth(f) => {
2754                let name = match self.config.dialect {
2755                    Some(DialectType::Presto)
2756                    | Some(DialectType::Trino)
2757                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2758                    _ => "DAYOFMONTH",
2759                };
2760                self.generate_simple_func(name, &f.this)
2761            }
2762            Expression::DayOfYear(f) => {
2763                let name = match self.config.dialect {
2764                    Some(DialectType::Presto)
2765                    | Some(DialectType::Trino)
2766                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2767                    _ => "DAYOFYEAR",
2768                };
2769                self.generate_simple_func(name, &f.this)
2770            }
2771            Expression::WeekOfYear(f) => {
2772                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2773                let name = match self.config.dialect {
2774                    Some(DialectType::Hive)
2775                    | Some(DialectType::DuckDB)
2776                    | Some(DialectType::Spark)
2777                    | Some(DialectType::Databricks)
2778                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2779                    _ => "WEEK_OF_YEAR",
2780                };
2781                self.generate_simple_func(name, &f.this)
2782            }
2783            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2784            Expression::AddMonths(f) => {
2785                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2786            }
2787            Expression::MonthsBetween(f) => {
2788                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2789            }
2790            Expression::LastDay(f) => self.generate_last_day(f),
2791            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2792            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2793            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2794            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2795            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2796            Expression::MakeDate(f) => self.generate_make_date(f),
2797            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2798            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2799
2800            // Array functions
2801            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2802            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2803            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2804            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2805            Expression::ArrayContains(f) => {
2806                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2807            }
2808            Expression::ArrayPosition(f) => {
2809                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2810            }
2811            Expression::ArrayAppend(f) => {
2812                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2813            }
2814            Expression::ArrayPrepend(f) => {
2815                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2816            }
2817            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2818            Expression::ArraySort(f) => self.generate_array_sort(f),
2819            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2820            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2821            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2822            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2823            Expression::Unnest(f) => self.generate_unnest(f),
2824            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2825            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2826            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2827            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2828            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2829            Expression::ArrayCompact(f) => {
2830                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2831                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2832                    self.write("LIST_FILTER(");
2833                    self.generate_expression(&f.this)?;
2834                    self.write(", _u -> NOT _u IS NULL)");
2835                    Ok(())
2836                } else {
2837                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2838                }
2839            }
2840            Expression::ArrayIntersect(f) => {
2841                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2842                self.generate_vararg_func(func_name, &f.expressions)
2843            }
2844            Expression::ArrayUnion(f) => {
2845                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2846            }
2847            Expression::ArrayExcept(f) => {
2848                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2849            }
2850            Expression::ArrayRemove(f) => {
2851                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2852            }
2853            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2854            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2855            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2856
2857            // Struct functions
2858            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2859            Expression::StructExtract(f) => self.generate_struct_extract(f),
2860            Expression::NamedStruct(f) => self.generate_named_struct(f),
2861
2862            // Map functions
2863            Expression::MapFunc(f) => self.generate_map_constructor(f),
2864            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2865            Expression::MapFromArrays(f) => {
2866                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2867            }
2868            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2869            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2870            Expression::MapContainsKey(f) => {
2871                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2872            }
2873            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2874            Expression::ElementAt(f) => {
2875                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2876            }
2877            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2878            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2879
2880            // JSON functions
2881            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2882            Expression::JsonExtractScalar(f) => {
2883                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2884            }
2885            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2886            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2887            Expression::JsonObject(f) => self.generate_json_object(f),
2888            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2889            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2890            Expression::JsonArrayLength(f) => {
2891                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2892            }
2893            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2894            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2895            Expression::ParseJson(f) => {
2896                let name = match self.config.dialect {
2897                    Some(DialectType::Presto)
2898                    | Some(DialectType::Trino)
2899                    | Some(DialectType::Athena) => "JSON_PARSE",
2900                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2901                        // PostgreSQL: CAST(x AS JSON)
2902                        self.write_keyword("CAST");
2903                        self.write("(");
2904                        self.generate_expression(&f.this)?;
2905                        self.write_keyword(" AS ");
2906                        self.write_keyword("JSON");
2907                        self.write(")");
2908                        return Ok(());
2909                    }
2910                    Some(DialectType::Hive)
2911                    | Some(DialectType::Spark)
2912                    | Some(DialectType::MySQL)
2913                    | Some(DialectType::SingleStore)
2914                    | Some(DialectType::TiDB)
2915                    | Some(DialectType::TSQL) => {
2916                        // Hive/Spark/MySQL/TSQL: just emit the string literal
2917                        self.generate_expression(&f.this)?;
2918                        return Ok(());
2919                    }
2920                    Some(DialectType::DuckDB) => "JSON",
2921                    _ => "PARSE_JSON",
2922                };
2923                self.generate_simple_func(name, &f.this)
2924            }
2925            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
2926            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
2927            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
2928            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
2929            Expression::JsonMergePatch(f) => {
2930                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
2931            }
2932            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
2933            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
2934
2935            // Type casting/conversion
2936            Expression::Convert(f) => self.generate_convert(f),
2937            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
2938
2939            // Additional expressions
2940            Expression::Lambda(f) => self.generate_lambda(f),
2941            Expression::Parameter(f) => self.generate_parameter(f),
2942            Expression::Placeholder(f) => self.generate_placeholder(f),
2943            Expression::NamedArgument(f) => self.generate_named_argument(f),
2944            Expression::TableArgument(f) => self.generate_table_argument(f),
2945            Expression::SqlComment(f) => self.generate_sql_comment(f),
2946
2947            // Additional predicates
2948            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
2949            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
2950            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
2951            Expression::SimilarTo(f) => self.generate_similar_to(f),
2952            Expression::Any(f) => self.generate_quantified("ANY", f),
2953            Expression::All(f) => self.generate_quantified("ALL", f),
2954            Expression::Overlaps(f) => self.generate_overlaps(f),
2955
2956            // Bitwise operations
2957            Expression::BitwiseLeftShift(op) => {
2958                if matches!(
2959                    self.config.dialect,
2960                    Some(DialectType::Presto) | Some(DialectType::Trino)
2961                ) {
2962                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
2963                    self.write("(");
2964                    self.generate_expression(&op.left)?;
2965                    self.write(", ");
2966                    self.generate_expression(&op.right)?;
2967                    self.write(")");
2968                    Ok(())
2969                } else if matches!(
2970                    self.config.dialect,
2971                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2972                ) {
2973                    self.write_keyword("SHIFTLEFT");
2974                    self.write("(");
2975                    self.generate_expression(&op.left)?;
2976                    self.write(", ");
2977                    self.generate_expression(&op.right)?;
2978                    self.write(")");
2979                    Ok(())
2980                } else {
2981                    self.generate_binary_op(op, "<<")
2982                }
2983            }
2984            Expression::BitwiseRightShift(op) => {
2985                if matches!(
2986                    self.config.dialect,
2987                    Some(DialectType::Presto) | Some(DialectType::Trino)
2988                ) {
2989                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
2990                    self.write("(");
2991                    self.generate_expression(&op.left)?;
2992                    self.write(", ");
2993                    self.generate_expression(&op.right)?;
2994                    self.write(")");
2995                    Ok(())
2996                } else if matches!(
2997                    self.config.dialect,
2998                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2999                ) {
3000                    self.write_keyword("SHIFTRIGHT");
3001                    self.write("(");
3002                    self.generate_expression(&op.left)?;
3003                    self.write(", ");
3004                    self.generate_expression(&op.right)?;
3005                    self.write(")");
3006                    Ok(())
3007                } else {
3008                    self.generate_binary_op(op, ">>")
3009                }
3010            }
3011            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3012            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3013            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3014
3015            // Array/struct/map access
3016            Expression::Subscript(s) => self.generate_subscript(s),
3017            Expression::Dot(d) => self.generate_dot_access(d),
3018            Expression::MethodCall(m) => self.generate_method_call(m),
3019            Expression::ArraySlice(s) => self.generate_array_slice(s),
3020
3021            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3022            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3023            Expression::Add(op) => self.generate_binary_op(op, "+"),
3024            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3025            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3026            Expression::Div(op) => self.generate_binary_op(op, "/"),
3027            Expression::IntDiv(f) => {
3028                use crate::dialects::DialectType;
3029                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3030                    // DuckDB uses // operator for integer division
3031                    self.generate_expression(&f.this)?;
3032                    self.write(" // ");
3033                    self.generate_expression(&f.expression)?;
3034                    Ok(())
3035                } else if matches!(
3036                    self.config.dialect,
3037                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3038                ) {
3039                    // Hive/Spark use DIV as an infix operator
3040                    self.generate_expression(&f.this)?;
3041                    self.write(" ");
3042                    self.write_keyword("DIV");
3043                    self.write(" ");
3044                    self.generate_expression(&f.expression)?;
3045                    Ok(())
3046                } else {
3047                    // Other dialects use DIV function
3048                    self.write_keyword("DIV");
3049                    self.write("(");
3050                    self.generate_expression(&f.this)?;
3051                    self.write(", ");
3052                    self.generate_expression(&f.expression)?;
3053                    self.write(")");
3054                    Ok(())
3055                }
3056            }
3057            Expression::Mod(op) => {
3058                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3059                    self.generate_binary_op(op, "MOD")
3060                } else {
3061                    self.generate_binary_op(op, "%")
3062                }
3063            }
3064            Expression::Eq(op) => self.generate_binary_op(op, "="),
3065            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3066            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3067            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3068            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3069            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3070            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3071            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3072            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3073            Expression::Concat(op) => {
3074                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3075                if self.config.dialect == Some(DialectType::Solr) {
3076                    self.generate_binary_op(op, "OR")
3077                } else if self.config.dialect == Some(DialectType::MySQL) {
3078                    self.generate_mysql_concat_from_concat(op)
3079                } else {
3080                    self.generate_binary_op(op, "||")
3081                }
3082            }
3083            Expression::BitwiseAnd(op) => {
3084                // Presto/Trino use BITWISE_AND function
3085                if matches!(
3086                    self.config.dialect,
3087                    Some(DialectType::Presto) | Some(DialectType::Trino)
3088                ) {
3089                    self.write_keyword("BITWISE_AND");
3090                    self.write("(");
3091                    self.generate_expression(&op.left)?;
3092                    self.write(", ");
3093                    self.generate_expression(&op.right)?;
3094                    self.write(")");
3095                    Ok(())
3096                } else {
3097                    self.generate_binary_op(op, "&")
3098                }
3099            }
3100            Expression::BitwiseOr(op) => {
3101                // Presto/Trino use BITWISE_OR function
3102                if matches!(
3103                    self.config.dialect,
3104                    Some(DialectType::Presto) | Some(DialectType::Trino)
3105                ) {
3106                    self.write_keyword("BITWISE_OR");
3107                    self.write("(");
3108                    self.generate_expression(&op.left)?;
3109                    self.write(", ");
3110                    self.generate_expression(&op.right)?;
3111                    self.write(")");
3112                    Ok(())
3113                } else {
3114                    self.generate_binary_op(op, "|")
3115                }
3116            }
3117            Expression::BitwiseXor(op) => {
3118                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3119                if matches!(
3120                    self.config.dialect,
3121                    Some(DialectType::Presto) | Some(DialectType::Trino)
3122                ) {
3123                    self.write_keyword("BITWISE_XOR");
3124                    self.write("(");
3125                    self.generate_expression(&op.left)?;
3126                    self.write(", ");
3127                    self.generate_expression(&op.right)?;
3128                    self.write(")");
3129                    Ok(())
3130                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3131                    self.generate_binary_op(op, "#")
3132                } else {
3133                    self.generate_binary_op(op, "^")
3134                }
3135            }
3136            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3137            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3138            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3139            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3140            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3141            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3142            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3143            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3144            Expression::JSONBContains(f) => {
3145                // PostgreSQL JSONB contains key operator: a ? b
3146                self.generate_expression(&f.this)?;
3147                self.write_space();
3148                self.write("?");
3149                self.write_space();
3150                self.generate_expression(&f.expression)
3151            }
3152            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3153            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3154            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3155            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3156            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3157            Expression::BitwiseNot(op) => {
3158                // Presto/Trino use BITWISE_NOT function
3159                if matches!(
3160                    self.config.dialect,
3161                    Some(DialectType::Presto) | Some(DialectType::Trino)
3162                ) {
3163                    self.write_keyword("BITWISE_NOT");
3164                    self.write("(");
3165                    self.generate_expression(&op.this)?;
3166                    self.write(")");
3167                    Ok(())
3168                } else {
3169                    self.generate_unary_op(op, "~")
3170                }
3171            }
3172            Expression::In(in_expr) => self.generate_in(in_expr),
3173            Expression::Between(between) => self.generate_between(between),
3174            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3175            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3176            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3177            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3178            Expression::Is(is_expr) => self.generate_is(is_expr),
3179            Expression::Exists(exists) => self.generate_exists(exists),
3180            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3181            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3182            Expression::Paren(paren) => {
3183                // JoinedTable already outputs its own parentheses, so don't double-wrap
3184                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3185
3186                if !skip_parens {
3187                    self.write("(");
3188                    if self.config.pretty {
3189                        self.write_newline();
3190                        self.indent_level += 1;
3191                        self.write_indent();
3192                    }
3193                }
3194                self.generate_expression(&paren.this)?;
3195                if !skip_parens {
3196                    if self.config.pretty {
3197                        self.write_newline();
3198                        self.indent_level -= 1;
3199                        self.write_indent();
3200                    }
3201                    self.write(")");
3202                }
3203                // Output trailing comments after closing paren
3204                for comment in &paren.trailing_comments {
3205                    self.write(" ");
3206                    self.write_formatted_comment(comment);
3207                }
3208                Ok(())
3209            }
3210            Expression::Array(arr) => self.generate_array(arr),
3211            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3212            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3213            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3214            Expression::DataType(dt) => self.generate_data_type(dt),
3215            Expression::Raw(raw) => {
3216                self.write(&raw.sql);
3217                Ok(())
3218            }
3219            Expression::Command(cmd) => {
3220                self.write(&cmd.this);
3221                Ok(())
3222            }
3223            Expression::Kill(kill) => {
3224                self.write_keyword("KILL");
3225                if let Some(kind) = &kill.kind {
3226                    self.write_space();
3227                    self.write_keyword(kind);
3228                }
3229                self.write_space();
3230                self.generate_expression(&kill.this)?;
3231                Ok(())
3232            }
3233            Expression::Execute(exec) => {
3234                self.write_keyword("EXEC");
3235                self.write_space();
3236                self.generate_expression(&exec.this)?;
3237                for (i, param) in exec.parameters.iter().enumerate() {
3238                    if i == 0 {
3239                        self.write_space();
3240                    } else {
3241                        self.write(", ");
3242                    }
3243                    self.write(&param.name);
3244                    self.write("=");
3245                    self.generate_expression(&param.value)?;
3246                }
3247                Ok(())
3248            }
3249            Expression::Annotated(annotated) => {
3250                self.generate_expression(&annotated.this)?;
3251                for comment in &annotated.trailing_comments {
3252                    self.write(" ");
3253                    self.write_formatted_comment(comment);
3254                }
3255                Ok(())
3256            }
3257
3258            // DDL statements
3259            Expression::CreateTable(ct) => self.generate_create_table(ct),
3260            Expression::DropTable(dt) => self.generate_drop_table(dt),
3261            Expression::AlterTable(at) => self.generate_alter_table(at),
3262            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3263            Expression::DropIndex(di) => self.generate_drop_index(di),
3264            Expression::CreateView(cv) => self.generate_create_view(cv),
3265            Expression::DropView(dv) => self.generate_drop_view(dv),
3266            Expression::AlterView(av) => self.generate_alter_view(av),
3267            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3268            Expression::Truncate(tr) => self.generate_truncate(tr),
3269            Expression::Use(u) => self.generate_use(u),
3270            // Phase 4: Additional DDL statements
3271            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3272            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3273            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3274            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3275            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3276            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3277            Expression::DropFunction(df) => self.generate_drop_function(df),
3278            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3279            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3280            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3281            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3282            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3283            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3284            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3285            Expression::CreateType(ct) => self.generate_create_type(ct),
3286            Expression::DropType(dt) => self.generate_drop_type(dt),
3287            Expression::Describe(d) => self.generate_describe(d),
3288            Expression::Show(s) => self.generate_show(s),
3289
3290            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3291            Expression::Cache(c) => self.generate_cache(c),
3292            Expression::Uncache(u) => self.generate_uncache(u),
3293            Expression::LoadData(l) => self.generate_load_data(l),
3294            Expression::Pragma(p) => self.generate_pragma(p),
3295            Expression::Grant(g) => self.generate_grant(g),
3296            Expression::Revoke(r) => self.generate_revoke(r),
3297            Expression::Comment(c) => self.generate_comment(c),
3298            Expression::SetStatement(s) => self.generate_set_statement(s),
3299
3300            // PIVOT/UNPIVOT
3301            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3302            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3303
3304            // VALUES table constructor
3305            Expression::Values(values) => self.generate_values(values),
3306
3307            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3308            Expression::AIAgg(e) => self.generate_ai_agg(e),
3309            Expression::AIClassify(e) => self.generate_ai_classify(e),
3310            Expression::AddPartition(e) => self.generate_add_partition(e),
3311            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3312            Expression::Aliases(e) => self.generate_aliases(e),
3313            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3314            Expression::AlterColumn(e) => self.generate_alter_column(e),
3315            Expression::AlterSession(e) => self.generate_alter_session(e),
3316            Expression::AlterSet(e) => self.generate_alter_set(e),
3317            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3318            Expression::Analyze(e) => self.generate_analyze(e),
3319            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3320            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3321            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3322            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3323            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3324            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3325            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3326            Expression::Anonymous(e) => self.generate_anonymous(e),
3327            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3328            Expression::Apply(e) => self.generate_apply(e),
3329            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3330            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3331            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3332            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3333            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3334            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3335            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3336            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3337            Expression::ArgMax(e) => self.generate_arg_max(e),
3338            Expression::ArgMin(e) => self.generate_arg_min(e),
3339            Expression::ArrayAll(e) => self.generate_array_all(e),
3340            Expression::ArrayAny(e) => self.generate_array_any(e),
3341            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3342            Expression::ArraySum(e) => self.generate_array_sum(e),
3343            Expression::AtIndex(e) => self.generate_at_index(e),
3344            Expression::Attach(e) => self.generate_attach(e),
3345            Expression::AttachOption(e) => self.generate_attach_option(e),
3346            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3347            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3348            Expression::BackupProperty(e) => self.generate_backup_property(e),
3349            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3350            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3351            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3352            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3353            Expression::Booland(e) => self.generate_booland(e),
3354            Expression::Boolor(e) => self.generate_boolor(e),
3355            Expression::BuildProperty(e) => self.generate_build_property(e),
3356            Expression::ByteString(e) => self.generate_byte_string(e),
3357            Expression::CaseSpecificColumnConstraint(e) => {
3358                self.generate_case_specific_column_constraint(e)
3359            }
3360            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3361            Expression::Changes(e) => self.generate_changes(e),
3362            Expression::CharacterSetColumnConstraint(e) => {
3363                self.generate_character_set_column_constraint(e)
3364            }
3365            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3366            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3367            Expression::CheckJson(e) => self.generate_check_json(e),
3368            Expression::CheckXml(e) => self.generate_check_xml(e),
3369            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3370            Expression::Clone(e) => self.generate_clone(e),
3371            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3372            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3373            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3374            Expression::CollateProperty(e) => self.generate_collate_property(e),
3375            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3376            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3377            Expression::ColumnPosition(e) => self.generate_column_position(e),
3378            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3379            Expression::Columns(e) => self.generate_columns(e),
3380            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3381            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3382            Expression::Commit(e) => self.generate_commit(e),
3383            Expression::Comprehension(e) => self.generate_comprehension(e),
3384            Expression::Compress(e) => self.generate_compress(e),
3385            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3386            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3387            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3388            Expression::Constraint(e) => self.generate_constraint(e),
3389            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3390            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3391            Expression::Copy(e) => self.generate_copy(e),
3392            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3393            Expression::Corr(e) => self.generate_corr(e),
3394            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3395            Expression::CovarPop(e) => self.generate_covar_pop(e),
3396            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3397            Expression::Credentials(e) => self.generate_credentials(e),
3398            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3399            Expression::Cte(e) => self.generate_cte(e),
3400            Expression::Cube(e) => self.generate_cube(e),
3401            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3402            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3403            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3404            Expression::CurrentUser(e) => self.generate_current_user(e),
3405            Expression::DPipe(e) => self.generate_d_pipe(e),
3406            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3407            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3408            Expression::Date(e) => self.generate_date_func(e),
3409            Expression::DateBin(e) => self.generate_date_bin(e),
3410            Expression::DateFormatColumnConstraint(e) => {
3411                self.generate_date_format_column_constraint(e)
3412            }
3413            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3414            Expression::Datetime(e) => self.generate_datetime(e),
3415            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3416            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3417            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3418            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3419            Expression::Dayname(e) => self.generate_dayname(e),
3420            Expression::Declare(e) => self.generate_declare(e),
3421            Expression::DeclareItem(e) => self.generate_declare_item(e),
3422            Expression::DecodeCase(e) => self.generate_decode_case(e),
3423            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3424            Expression::DecompressString(e) => self.generate_decompress_string(e),
3425            Expression::Decrypt(e) => self.generate_decrypt(e),
3426            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3427            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3428            Expression::Detach(e) => self.generate_detach(e),
3429            Expression::DictProperty(e) => self.generate_dict_property(e),
3430            Expression::DictRange(e) => self.generate_dict_range(e),
3431            Expression::Directory(e) => self.generate_directory(e),
3432            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3433            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3434            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3435            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3436            Expression::DotProduct(e) => self.generate_dot_product(e),
3437            Expression::DropPartition(e) => self.generate_drop_partition(e),
3438            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3439            Expression::Elt(e) => self.generate_elt(e),
3440            Expression::Encode(e) => self.generate_encode(e),
3441            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3442            Expression::Encrypt(e) => self.generate_encrypt(e),
3443            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3444            Expression::EngineProperty(e) => self.generate_engine_property(e),
3445            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3446            Expression::EphemeralColumnConstraint(e) => {
3447                self.generate_ephemeral_column_constraint(e)
3448            }
3449            Expression::EqualNull(e) => self.generate_equal_null(e),
3450            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3451            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3452            Expression::Export(e) => self.generate_export(e),
3453            Expression::ExternalProperty(e) => self.generate_external_property(e),
3454            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3455            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3456            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3457            Expression::Fetch(e) => self.generate_fetch(e),
3458            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3459            Expression::Filter(e) => self.generate_filter(e),
3460            Expression::Float64(e) => self.generate_float64(e),
3461            Expression::ForIn(e) => self.generate_for_in(e),
3462            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3463            Expression::Format(e) => self.generate_format(e),
3464            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3465            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3466            Expression::From(e) => self.generate_from(e),
3467            Expression::FromBase(e) => self.generate_from_base(e),
3468            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3469            Expression::GapFill(e) => self.generate_gap_fill(e),
3470            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3471            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3472            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3473            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3474            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3475                self.generate_generated_as_identity_column_constraint(e)
3476            }
3477            Expression::GeneratedAsRowColumnConstraint(e) => {
3478                self.generate_generated_as_row_column_constraint(e)
3479            }
3480            Expression::Get(e) => self.generate_get(e),
3481            Expression::GetExtract(e) => self.generate_get_extract(e),
3482            Expression::Getbit(e) => self.generate_getbit(e),
3483            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3484            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3485            Expression::Group(e) => self.generate_group(e),
3486            Expression::GroupBy(e) => self.generate_group_by(e),
3487            Expression::Grouping(e) => self.generate_grouping(e),
3488            Expression::GroupingId(e) => self.generate_grouping_id(e),
3489            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3490            Expression::HashAgg(e) => self.generate_hash_agg(e),
3491            Expression::Having(e) => self.generate_having(e),
3492            Expression::HavingMax(e) => self.generate_having_max(e),
3493            Expression::Heredoc(e) => self.generate_heredoc(e),
3494            Expression::HexEncode(e) => self.generate_hex_encode(e),
3495            Expression::Hll(e) => self.generate_hll(e),
3496            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3497            Expression::IncludeProperty(e) => self.generate_include_property(e),
3498            Expression::Index(e) => self.generate_index(e),
3499            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3500            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3501            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3502            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3503            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3504            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3505            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3506            Expression::Install(e) => self.generate_install(e),
3507            Expression::IntervalOp(e) => self.generate_interval_op(e),
3508            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3509            Expression::IntoClause(e) => self.generate_into_clause(e),
3510            Expression::Introducer(e) => self.generate_introducer(e),
3511            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3512            Expression::JSON(e) => self.generate_json(e),
3513            Expression::JSONArray(e) => self.generate_json_array(e),
3514            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3515            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3516            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3517            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3518            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3519            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3520            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3521            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3522            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3523            Expression::JSONExists(e) => self.generate_json_exists(e),
3524            Expression::JSONCast(e) => self.generate_json_cast(e),
3525            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3526            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3527            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3528            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3529            Expression::JSONFormat(e) => self.generate_json_format(e),
3530            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3531            Expression::JSONKeys(e) => self.generate_json_keys(e),
3532            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3533            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3534            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3535            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3536            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3537            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3538            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3539            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3540            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3541            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3542            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3543            Expression::JSONRemove(e) => self.generate_json_remove(e),
3544            Expression::JSONSchema(e) => self.generate_json_schema(e),
3545            Expression::JSONSet(e) => self.generate_json_set(e),
3546            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3547            Expression::JSONTable(e) => self.generate_json_table(e),
3548            Expression::JSONType(e) => self.generate_json_type(e),
3549            Expression::JSONValue(e) => self.generate_json_value(e),
3550            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3551            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3552            Expression::JoinHint(e) => self.generate_join_hint(e),
3553            Expression::JournalProperty(e) => self.generate_journal_property(e),
3554            Expression::LanguageProperty(e) => self.generate_language_property(e),
3555            Expression::Lateral(e) => self.generate_lateral(e),
3556            Expression::LikeProperty(e) => self.generate_like_property(e),
3557            Expression::Limit(e) => self.generate_limit(e),
3558            Expression::LimitOptions(e) => self.generate_limit_options(e),
3559            Expression::List(e) => self.generate_list(e),
3560            Expression::ToMap(e) => self.generate_tomap(e),
3561            Expression::Localtime(e) => self.generate_localtime(e),
3562            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3563            Expression::LocationProperty(e) => self.generate_location_property(e),
3564            Expression::Lock(e) => self.generate_lock(e),
3565            Expression::LockProperty(e) => self.generate_lock_property(e),
3566            Expression::LockingProperty(e) => self.generate_locking_property(e),
3567            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3568            Expression::LogProperty(e) => self.generate_log_property(e),
3569            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3570            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3571            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3572            Expression::MakeInterval(e) => self.generate_make_interval(e),
3573            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3574            Expression::Map(e) => self.generate_map(e),
3575            Expression::MapCat(e) => self.generate_map_cat(e),
3576            Expression::MapDelete(e) => self.generate_map_delete(e),
3577            Expression::MapInsert(e) => self.generate_map_insert(e),
3578            Expression::MapPick(e) => self.generate_map_pick(e),
3579            Expression::MaskingPolicyColumnConstraint(e) => {
3580                self.generate_masking_policy_column_constraint(e)
3581            }
3582            Expression::MatchAgainst(e) => self.generate_match_against(e),
3583            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3584            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3585            Expression::Merge(e) => self.generate_merge(e),
3586            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3587            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3588            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3589            Expression::Minhash(e) => self.generate_minhash(e),
3590            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3591            Expression::Monthname(e) => self.generate_monthname(e),
3592            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3593            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3594            Expression::Normal(e) => self.generate_normal(e),
3595            Expression::Normalize(e) => self.generate_normalize(e),
3596            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3597            Expression::Nullif(e) => self.generate_nullif(e),
3598            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3599            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3600            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3601            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3602            Expression::Offset(e) => self.generate_offset(e),
3603            Expression::Qualify(e) => self.generate_qualify(e),
3604            Expression::OnCluster(e) => self.generate_on_cluster(e),
3605            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3606            Expression::OnCondition(e) => self.generate_on_condition(e),
3607            Expression::OnConflict(e) => self.generate_on_conflict(e),
3608            Expression::OnProperty(e) => self.generate_on_property(e),
3609            Expression::Opclass(e) => self.generate_opclass(e),
3610            Expression::OpenJSON(e) => self.generate_open_json(e),
3611            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3612            Expression::Operator(e) => self.generate_operator(e),
3613            Expression::OrderBy(e) => self.generate_order_by(e),
3614            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3615            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3616            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3617            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3618            Expression::ParseIp(e) => self.generate_parse_ip(e),
3619            Expression::ParseJSON(e) => self.generate_parse_json(e),
3620            Expression::ParseTime(e) => self.generate_parse_time(e),
3621            Expression::ParseUrl(e) => self.generate_parse_url(e),
3622            Expression::Partition(e) => self.generate_partition_expr(e),
3623            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3624            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3625            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3626            Expression::PartitionByRangePropertyDynamic(e) => {
3627                self.generate_partition_by_range_property_dynamic(e)
3628            }
3629            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3630            Expression::PartitionList(e) => self.generate_partition_list(e),
3631            Expression::PartitionRange(e) => self.generate_partition_range(e),
3632            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3633            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3634            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3635            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3636            Expression::PeriodForSystemTimeConstraint(e) => {
3637                self.generate_period_for_system_time_constraint(e)
3638            }
3639            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3640            Expression::PivotAny(e) => self.generate_pivot_any(e),
3641            Expression::Predict(e) => self.generate_predict(e),
3642            Expression::PreviousDay(e) => self.generate_previous_day(e),
3643            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3644            Expression::PrimaryKeyColumnConstraint(e) => {
3645                self.generate_primary_key_column_constraint(e)
3646            }
3647            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3648            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3649            Expression::OptionsProperty(e) => self.generate_options_property(e),
3650            Expression::Properties(e) => self.generate_properties(e),
3651            Expression::Property(e) => self.generate_property(e),
3652            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3653            Expression::Put(e) => self.generate_put(e),
3654            Expression::Quantile(e) => self.generate_quantile(e),
3655            Expression::QueryBand(e) => self.generate_query_band(e),
3656            Expression::QueryOption(e) => self.generate_query_option(e),
3657            Expression::QueryTransform(e) => self.generate_query_transform(e),
3658            Expression::Randn(e) => self.generate_randn(e),
3659            Expression::Randstr(e) => self.generate_randstr(e),
3660            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3661            Expression::RangeN(e) => self.generate_range_n(e),
3662            Expression::ReadCSV(e) => self.generate_read_csv(e),
3663            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3664            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3665            Expression::Reduce(e) => self.generate_reduce(e),
3666            Expression::Reference(e) => self.generate_reference(e),
3667            Expression::Refresh(e) => self.generate_refresh(e),
3668            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3669            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3670            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3671            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3672            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3673            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3674            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3675            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3676            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3677            Expression::RegrCount(e) => self.generate_regr_count(e),
3678            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3679            Expression::RegrR2(e) => self.generate_regr_r2(e),
3680            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3681            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3682            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3683            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3684            Expression::RegrValx(e) => self.generate_regr_valx(e),
3685            Expression::RegrValy(e) => self.generate_regr_valy(e),
3686            Expression::RemoteWithConnectionModelProperty(e) => {
3687                self.generate_remote_with_connection_model_property(e)
3688            }
3689            Expression::RenameColumn(e) => self.generate_rename_column(e),
3690            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3691            Expression::Returning(e) => self.generate_returning(e),
3692            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3693            Expression::Rollback(e) => self.generate_rollback(e),
3694            Expression::Rollup(e) => self.generate_rollup(e),
3695            Expression::RowFormatDelimitedProperty(e) => {
3696                self.generate_row_format_delimited_property(e)
3697            }
3698            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3699            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3700            Expression::SHA2(e) => self.generate_sha2(e),
3701            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3702            Expression::SafeAdd(e) => self.generate_safe_add(e),
3703            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3704            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3705            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3706            Expression::SampleProperty(e) => self.generate_sample_property(e),
3707            Expression::Schema(e) => self.generate_schema(e),
3708            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3709            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3710            Expression::Search(e) => self.generate_search(e),
3711            Expression::SearchIp(e) => self.generate_search_ip(e),
3712            Expression::SecurityProperty(e) => self.generate_security_property(e),
3713            Expression::SemanticView(e) => self.generate_semantic_view(e),
3714            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3715            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3716            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3717            Expression::Set(e) => self.generate_set(e),
3718            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3719            Expression::SetItem(e) => self.generate_set_item(e),
3720            Expression::SetOperation(e) => self.generate_set_operation(e),
3721            Expression::SetProperty(e) => self.generate_set_property(e),
3722            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3723            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3724            Expression::Slice(e) => self.generate_slice(e),
3725            Expression::SortArray(e) => self.generate_sort_array(e),
3726            Expression::SortBy(e) => self.generate_sort_by(e),
3727            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3728            Expression::SplitPart(e) => self.generate_split_part(e),
3729            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3730            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3731            Expression::StDistance(e) => self.generate_st_distance(e),
3732            Expression::StPoint(e) => self.generate_st_point(e),
3733            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3734            Expression::StandardHash(e) => self.generate_standard_hash(e),
3735            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3736            Expression::StrPosition(e) => self.generate_str_position(e),
3737            Expression::StrToDate(e) => self.generate_str_to_date(e),
3738            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3739            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3740            Expression::StrToMap(e) => self.generate_str_to_map(e),
3741            Expression::StrToTime(e) => self.generate_str_to_time(e),
3742            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3743            Expression::StringToArray(e) => self.generate_string_to_array(e),
3744            Expression::Struct(e) => self.generate_struct(e),
3745            Expression::Stuff(e) => self.generate_stuff(e),
3746            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3747            Expression::Summarize(e) => self.generate_summarize(e),
3748            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3749            Expression::TableAlias(e) => self.generate_table_alias(e),
3750            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3751            Expression::RowsFrom(e) => self.generate_rows_from(e),
3752            Expression::TableSample(e) => self.generate_table_sample(e),
3753            Expression::Tag(e) => self.generate_tag(e),
3754            Expression::Tags(e) => self.generate_tags(e),
3755            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3756            Expression::Time(e) => self.generate_time_func(e),
3757            Expression::TimeAdd(e) => self.generate_time_add(e),
3758            Expression::TimeDiff(e) => self.generate_time_diff(e),
3759            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3760            Expression::TimeSlice(e) => self.generate_time_slice(e),
3761            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3762            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3763            Expression::TimeSub(e) => self.generate_time_sub(e),
3764            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3765            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3766            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3767            Expression::TimeUnit(e) => self.generate_time_unit(e),
3768            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3769            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3770            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3771            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3772            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3773            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3774            Expression::ToBinary(e) => self.generate_to_binary(e),
3775            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3776            Expression::ToChar(e) => self.generate_to_char(e),
3777            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3778            Expression::ToDouble(e) => self.generate_to_double(e),
3779            Expression::ToFile(e) => self.generate_to_file(e),
3780            Expression::ToNumber(e) => self.generate_to_number(e),
3781            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3782            Expression::Transaction(e) => self.generate_transaction(e),
3783            Expression::Transform(e) => self.generate_transform(e),
3784            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3785            Expression::TransientProperty(e) => self.generate_transient_property(e),
3786            Expression::Translate(e) => self.generate_translate(e),
3787            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3788            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3789            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3790            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3791            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3792            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3793            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3794            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3795            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3796            Expression::Unhex(e) => self.generate_unhex(e),
3797            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3798            Expression::Uniform(e) => self.generate_uniform(e),
3799            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3800            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3801            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3802            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3803            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3804            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3805            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3806            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3807            Expression::UtcTime(e) => self.generate_utc_time(e),
3808            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3809            Expression::Uuid(e) => self.generate_uuid(e),
3810            Expression::Var(v) => {
3811                if matches!(self.config.dialect, Some(DialectType::MySQL))
3812                    && v.this.len() > 2
3813                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3814                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3815                {
3816                    return self.generate_identifier(&Identifier {
3817                        name: v.this.clone(),
3818                        quoted: true,
3819                        trailing_comments: Vec::new(),
3820                        span: None,
3821                    });
3822                }
3823                self.write(&v.this);
3824                Ok(())
3825            }
3826            Expression::Variadic(e) => {
3827                self.write_keyword("VARIADIC");
3828                self.write_space();
3829                self.generate_expression(&e.this)?;
3830                Ok(())
3831            }
3832            Expression::VarMap(e) => self.generate_var_map(e),
3833            Expression::VectorSearch(e) => self.generate_vector_search(e),
3834            Expression::Version(e) => self.generate_version(e),
3835            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3836            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3837            Expression::WatermarkColumnConstraint(e) => {
3838                self.generate_watermark_column_constraint(e)
3839            }
3840            Expression::Week(e) => self.generate_week(e),
3841            Expression::When(e) => self.generate_when(e),
3842            Expression::Whens(e) => self.generate_whens(e),
3843            Expression::Where(e) => self.generate_where(e),
3844            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3845            Expression::Window(e) => self.generate_window(e),
3846            Expression::WindowSpec(e) => self.generate_window_spec(e),
3847            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3848            Expression::WithFill(e) => self.generate_with_fill(e),
3849            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3850            Expression::WithOperator(e) => self.generate_with_operator(e),
3851            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3852            Expression::WithSchemaBindingProperty(e) => {
3853                self.generate_with_schema_binding_property(e)
3854            }
3855            Expression::WithSystemVersioningProperty(e) => {
3856                self.generate_with_system_versioning_property(e)
3857            }
3858            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3859            Expression::XMLElement(e) => self.generate_xml_element(e),
3860            Expression::XMLGet(e) => self.generate_xml_get(e),
3861            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3862            Expression::XMLTable(e) => self.generate_xml_table(e),
3863            Expression::Xor(e) => self.generate_xor(e),
3864            Expression::Zipf(e) => self.generate_zipf(e),
3865            _ => {
3866                // Fallback for unimplemented expressions
3867                self.write(&format!("/* unimplemented: {:?} */", expr));
3868                Ok(())
3869            }
3870        }
3871    }
3872
3873    fn generate_select(&mut self, select: &Select) -> Result<()> {
3874        use crate::dialects::DialectType;
3875
3876        // Output leading comments before SELECT
3877        for comment in &select.leading_comments {
3878            self.write_formatted_comment(comment);
3879            self.write(" ");
3880        }
3881
3882        // WITH clause
3883        if let Some(with) = &select.with {
3884            self.generate_with(with)?;
3885            if self.config.pretty {
3886                self.write_newline();
3887                self.write_indent();
3888            } else {
3889                self.write_space();
3890            }
3891        }
3892
3893        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3894        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3895        for comment in &select.post_select_comments {
3896            self.write_formatted_comment(comment);
3897            self.write(" ");
3898        }
3899
3900        self.write_keyword("SELECT");
3901
3902        // Generate query hint if present /*+ ... */
3903        if let Some(hint) = &select.hint {
3904            self.generate_hint(hint)?;
3905        }
3906
3907        // For SQL Server, convert LIMIT to TOP (structural transformation)
3908        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3909        // TOP clause (SQL Server style - before DISTINCT)
3910        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3911            && select.top.is_none()
3912            && select.limit.is_some()
3913            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3914
3915        // For TOP-supporting dialects: DISTINCT before TOP
3916        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3917        let is_top_dialect = matches!(
3918            self.config.dialect,
3919            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3920        );
3921        let keep_top_verbatim = !is_top_dialect
3922            && select.limit.is_none()
3923            && select
3924                .top
3925                .as_ref()
3926                .map_or(false, |top| top.percent || top.with_ties);
3927
3928        if select.distinct && (is_top_dialect || select.top.is_some()) {
3929            self.write_space();
3930            self.write_keyword("DISTINCT");
3931        }
3932
3933        if is_top_dialect || keep_top_verbatim {
3934            if let Some(top) = &select.top {
3935                self.write_space();
3936                self.write_keyword("TOP");
3937                if top.parenthesized {
3938                    self.write(" (");
3939                    self.generate_expression(&top.this)?;
3940                    self.write(")");
3941                } else {
3942                    self.write_space();
3943                    self.generate_expression(&top.this)?;
3944                }
3945                if top.percent {
3946                    self.write_space();
3947                    self.write_keyword("PERCENT");
3948                }
3949                if top.with_ties {
3950                    self.write_space();
3951                    self.write_keyword("WITH TIES");
3952                }
3953            } else if use_top_from_limit {
3954                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3955                if let Some(limit) = &select.limit {
3956                    self.write_space();
3957                    self.write_keyword("TOP");
3958                    // Use parentheses for complex expressions, but not for simple literals
3959                    let is_simple_literal =
3960                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3961                    if is_simple_literal {
3962                        self.write_space();
3963                        self.generate_expression(&limit.this)?;
3964                    } else {
3965                        self.write(" (");
3966                        self.generate_expression(&limit.this)?;
3967                        self.write(")");
3968                    }
3969                }
3970            }
3971        }
3972
3973        if select.distinct && !is_top_dialect && select.top.is_none() {
3974            self.write_space();
3975            self.write_keyword("DISTINCT");
3976        }
3977
3978        // DISTINCT ON clause (PostgreSQL)
3979        if let Some(distinct_on) = &select.distinct_on {
3980            self.write_space();
3981            self.write_keyword("ON");
3982            self.write(" (");
3983            for (i, expr) in distinct_on.iter().enumerate() {
3984                if i > 0 {
3985                    self.write(", ");
3986                }
3987                self.generate_expression(expr)?;
3988            }
3989            self.write(")");
3990        }
3991
3992        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3993        for modifier in &select.operation_modifiers {
3994            self.write_space();
3995            self.write_keyword(modifier);
3996        }
3997
3998        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3999        if let Some(kind) = &select.kind {
4000            self.write_space();
4001            self.write_keyword("AS");
4002            self.write_space();
4003            self.write_keyword(kind);
4004        }
4005
4006        // Expressions (only if there are any)
4007        if !select.expressions.is_empty() {
4008            if self.config.pretty {
4009                self.write_newline();
4010                self.indent_level += 1;
4011            } else {
4012                self.write_space();
4013            }
4014        }
4015
4016        for (i, expr) in select.expressions.iter().enumerate() {
4017            if i > 0 {
4018                self.write(",");
4019                if self.config.pretty {
4020                    self.write_newline();
4021                } else {
4022                    self.write_space();
4023                }
4024            }
4025            if self.config.pretty {
4026                self.write_indent();
4027            }
4028            self.generate_expression(expr)?;
4029        }
4030
4031        if self.config.pretty && !select.expressions.is_empty() {
4032            self.indent_level -= 1;
4033        }
4034
4035        // INTO clause (SELECT ... INTO table_name)
4036        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4037        if let Some(into) = &select.into {
4038            if self.config.pretty {
4039                self.write_newline();
4040                self.write_indent();
4041            } else {
4042                self.write_space();
4043            }
4044            if into.bulk_collect {
4045                self.write_keyword("BULK COLLECT INTO");
4046            } else {
4047                self.write_keyword("INTO");
4048            }
4049            if into.temporary {
4050                self.write_space();
4051                self.write_keyword("TEMPORARY");
4052            }
4053            if into.unlogged {
4054                self.write_space();
4055                self.write_keyword("UNLOGGED");
4056            }
4057            self.write_space();
4058            // If we have multiple expressions, output them comma-separated
4059            if !into.expressions.is_empty() {
4060                for (i, expr) in into.expressions.iter().enumerate() {
4061                    if i > 0 {
4062                        self.write(", ");
4063                    }
4064                    self.generate_expression(expr)?;
4065                }
4066            } else {
4067                self.generate_expression(&into.this)?;
4068            }
4069        }
4070
4071        // FROM clause
4072        if let Some(from) = &select.from {
4073            if self.config.pretty {
4074                self.write_newline();
4075                self.write_indent();
4076            } else {
4077                self.write_space();
4078            }
4079            self.write_keyword("FROM");
4080            self.write_space();
4081
4082            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4083            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4084            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4085            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4086            let has_tablesample = from
4087                .expressions
4088                .iter()
4089                .any(|e| matches!(e, Expression::TableSample(_)));
4090            let is_cross_join_dialect = matches!(
4091                self.config.dialect,
4092                Some(DialectType::BigQuery)
4093                    | Some(DialectType::Hive)
4094                    | Some(DialectType::Spark)
4095                    | Some(DialectType::Databricks)
4096                    | Some(DialectType::SQLite)
4097                    | Some(DialectType::ClickHouse)
4098            );
4099            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4100            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4101            let source_is_same_as_target = self.config.source_dialect.is_some()
4102                && self.config.source_dialect == self.config.dialect;
4103            let source_is_cross_join_dialect = matches!(
4104                self.config.source_dialect,
4105                Some(DialectType::BigQuery)
4106                    | Some(DialectType::Hive)
4107                    | Some(DialectType::Spark)
4108                    | Some(DialectType::Databricks)
4109                    | Some(DialectType::SQLite)
4110                    | Some(DialectType::ClickHouse)
4111            );
4112            let use_cross_join = !has_tablesample
4113                && is_cross_join_dialect
4114                && (source_is_same_as_target
4115                    || source_is_cross_join_dialect
4116                    || self.config.source_dialect.is_none());
4117
4118            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4119            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4120
4121            for (i, expr) in from.expressions.iter().enumerate() {
4122                if i > 0 {
4123                    if use_cross_join {
4124                        self.write(" CROSS JOIN ");
4125                    } else {
4126                        self.write(", ");
4127                    }
4128                }
4129                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4130                    self.write("(");
4131                    self.generate_expression(expr)?;
4132                    self.write(")");
4133                } else {
4134                    self.generate_expression(expr)?;
4135                }
4136            }
4137        }
4138
4139        // JOINs - handle nested join structure for pretty printing
4140        // Deferred-condition joins "own" the non-deferred joins that follow them
4141        // until the next deferred join or end of list
4142        if self.config.pretty {
4143            self.generate_joins_with_nesting(&select.joins)?;
4144        } else {
4145            for join in &select.joins {
4146                self.generate_join(join)?;
4147            }
4148            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4149            for join in select.joins.iter().rev() {
4150                if join.deferred_condition {
4151                    self.generate_join_condition(join)?;
4152                }
4153            }
4154        }
4155
4156        // LATERAL VIEW clauses (Hive/Spark)
4157        for lateral_view in &select.lateral_views {
4158            self.generate_lateral_view(lateral_view)?;
4159        }
4160
4161        // PREWHERE (ClickHouse)
4162        if let Some(prewhere) = &select.prewhere {
4163            self.write_clause_condition("PREWHERE", prewhere)?;
4164        }
4165
4166        // WHERE
4167        if let Some(where_clause) = &select.where_clause {
4168            self.write_clause_condition("WHERE", &where_clause.this)?;
4169        }
4170
4171        // CONNECT BY (Oracle hierarchical queries)
4172        if let Some(connect) = &select.connect {
4173            self.generate_connect(connect)?;
4174        }
4175
4176        // GROUP BY
4177        if let Some(group_by) = &select.group_by {
4178            if self.config.pretty {
4179                // Output leading comments on their own lines before GROUP BY
4180                for comment in &group_by.comments {
4181                    self.write_newline();
4182                    self.write_indent();
4183                    self.write_formatted_comment(comment);
4184                }
4185                self.write_newline();
4186                self.write_indent();
4187            } else {
4188                self.write_space();
4189                // In non-pretty mode, output comments inline
4190                for comment in &group_by.comments {
4191                    self.write_formatted_comment(comment);
4192                    self.write_space();
4193                }
4194            }
4195            self.write_keyword("GROUP BY");
4196            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4197            match group_by.all {
4198                Some(true) => {
4199                    self.write_space();
4200                    self.write_keyword("ALL");
4201                }
4202                Some(false) => {
4203                    self.write_space();
4204                    self.write_keyword("DISTINCT");
4205                }
4206                None => {}
4207            }
4208            if !group_by.expressions.is_empty() {
4209                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4210                // These are represented as Cube/Rollup expressions with empty expressions at the end
4211                let mut trailing_cube = false;
4212                let mut trailing_rollup = false;
4213                let mut plain_expressions: Vec<&Expression> = Vec::new();
4214                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4215                let mut cube_expressions: Vec<&Expression> = Vec::new();
4216                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4217
4218                for expr in &group_by.expressions {
4219                    match expr {
4220                        Expression::Cube(c) if c.expressions.is_empty() => {
4221                            trailing_cube = true;
4222                        }
4223                        Expression::Rollup(r) if r.expressions.is_empty() => {
4224                            trailing_rollup = true;
4225                        }
4226                        Expression::Function(f) if f.name == "CUBE" => {
4227                            cube_expressions.push(expr);
4228                        }
4229                        Expression::Function(f) if f.name == "ROLLUP" => {
4230                            rollup_expressions.push(expr);
4231                        }
4232                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4233                            grouping_sets_expressions.push(expr);
4234                        }
4235                        _ => {
4236                            plain_expressions.push(expr);
4237                        }
4238                    }
4239                }
4240
4241                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4242                let mut regular_expressions: Vec<&Expression> = Vec::new();
4243                regular_expressions.extend(plain_expressions);
4244                regular_expressions.extend(grouping_sets_expressions);
4245                regular_expressions.extend(cube_expressions);
4246                regular_expressions.extend(rollup_expressions);
4247
4248                if self.config.pretty {
4249                    self.write_newline();
4250                    self.indent_level += 1;
4251                    self.write_indent();
4252                } else {
4253                    self.write_space();
4254                }
4255
4256                for (i, expr) in regular_expressions.iter().enumerate() {
4257                    if i > 0 {
4258                        if self.config.pretty {
4259                            self.write(",");
4260                            self.write_newline();
4261                            self.write_indent();
4262                        } else {
4263                            self.write(", ");
4264                        }
4265                    }
4266                    self.generate_expression(expr)?;
4267                }
4268
4269                if self.config.pretty {
4270                    self.indent_level -= 1;
4271                }
4272
4273                // Output trailing WITH CUBE or WITH ROLLUP
4274                if trailing_cube {
4275                    self.write_space();
4276                    self.write_keyword("WITH CUBE");
4277                } else if trailing_rollup {
4278                    self.write_space();
4279                    self.write_keyword("WITH ROLLUP");
4280                }
4281            }
4282
4283            // ClickHouse: WITH TOTALS
4284            if group_by.totals {
4285                self.write_space();
4286                self.write_keyword("WITH TOTALS");
4287            }
4288        }
4289
4290        // HAVING
4291        if let Some(having) = &select.having {
4292            if self.config.pretty {
4293                // Output leading comments on their own lines before HAVING
4294                for comment in &having.comments {
4295                    self.write_newline();
4296                    self.write_indent();
4297                    self.write_formatted_comment(comment);
4298                }
4299            } else {
4300                for comment in &having.comments {
4301                    self.write_space();
4302                    self.write_formatted_comment(comment);
4303                }
4304            }
4305            self.write_clause_condition("HAVING", &having.this)?;
4306        }
4307
4308        // QUALIFY and WINDOW clause ordering depends on input SQL
4309        if select.qualify_after_window {
4310            // WINDOW before QUALIFY (DuckDB style)
4311            if let Some(windows) = &select.windows {
4312                self.write_window_clause(windows)?;
4313            }
4314            if let Some(qualify) = &select.qualify {
4315                self.write_clause_condition("QUALIFY", &qualify.this)?;
4316            }
4317        } else {
4318            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4319            if let Some(qualify) = &select.qualify {
4320                self.write_clause_condition("QUALIFY", &qualify.this)?;
4321            }
4322            if let Some(windows) = &select.windows {
4323                self.write_window_clause(windows)?;
4324            }
4325        }
4326
4327        // DISTRIBUTE BY (Hive/Spark)
4328        if let Some(distribute_by) = &select.distribute_by {
4329            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4330        }
4331
4332        // CLUSTER BY (Hive/Spark)
4333        if let Some(cluster_by) = &select.cluster_by {
4334            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4335        }
4336
4337        // SORT BY (Hive/Spark - comes before ORDER BY)
4338        if let Some(sort_by) = &select.sort_by {
4339            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4340        }
4341
4342        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4343        if let Some(order_by) = &select.order_by {
4344            if self.config.pretty {
4345                // Output leading comments on their own lines before ORDER BY
4346                for comment in &order_by.comments {
4347                    self.write_newline();
4348                    self.write_indent();
4349                    self.write_formatted_comment(comment);
4350                }
4351            } else {
4352                for comment in &order_by.comments {
4353                    self.write_space();
4354                    self.write_formatted_comment(comment);
4355                }
4356            }
4357            let keyword = if order_by.siblings {
4358                "ORDER SIBLINGS BY"
4359            } else {
4360                "ORDER BY"
4361            };
4362            self.write_order_clause(keyword, &order_by.expressions)?;
4363        }
4364
4365        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4366        if select.order_by.is_none()
4367            && select.fetch.is_some()
4368            && matches!(
4369                self.config.dialect,
4370                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4371            )
4372        {
4373            if self.config.pretty {
4374                self.write_newline();
4375                self.write_indent();
4376            } else {
4377                self.write_space();
4378            }
4379            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4380        }
4381
4382        // LIMIT and OFFSET
4383        // PostgreSQL and others use: LIMIT count OFFSET offset
4384        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4385        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4386        let is_presto_like = matches!(
4387            self.config.dialect,
4388            Some(DialectType::Presto) | Some(DialectType::Trino)
4389        );
4390
4391        if is_presto_like && select.offset.is_some() {
4392            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4393            if let Some(offset) = &select.offset {
4394                if self.config.pretty {
4395                    self.write_newline();
4396                    self.write_indent();
4397                } else {
4398                    self.write_space();
4399                }
4400                self.write_keyword("OFFSET");
4401                self.write_space();
4402                self.write_limit_expr(&offset.this)?;
4403                if offset.rows == Some(true) {
4404                    self.write_space();
4405                    self.write_keyword("ROWS");
4406                }
4407            }
4408            if let Some(limit) = &select.limit {
4409                if self.config.pretty {
4410                    self.write_newline();
4411                    self.write_indent();
4412                } else {
4413                    self.write_space();
4414                }
4415                self.write_keyword("LIMIT");
4416                self.write_space();
4417                self.write_limit_expr(&limit.this)?;
4418                if limit.percent {
4419                    self.write_space();
4420                    self.write_keyword("PERCENT");
4421                }
4422                // Emit any comments that were captured from before the LIMIT keyword
4423                for comment in &limit.comments {
4424                    self.write(" ");
4425                    self.write_formatted_comment(comment);
4426                }
4427            }
4428        } else {
4429            // Check if FETCH will be converted to LIMIT (used for ordering)
4430            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4431                !fetch.percent
4432                    && !fetch.with_ties
4433                    && fetch.count.is_some()
4434                    && matches!(
4435                        self.config.dialect,
4436                        Some(DialectType::Spark)
4437                            | Some(DialectType::Hive)
4438                            | Some(DialectType::DuckDB)
4439                            | Some(DialectType::SQLite)
4440                            | Some(DialectType::MySQL)
4441                            | Some(DialectType::BigQuery)
4442                            | Some(DialectType::Databricks)
4443                            | Some(DialectType::StarRocks)
4444                            | Some(DialectType::Doris)
4445                            | Some(DialectType::Athena)
4446                            | Some(DialectType::ClickHouse)
4447                            | Some(DialectType::Redshift)
4448                    )
4449            });
4450
4451            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4452            if let Some(limit) = &select.limit {
4453                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4454                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4455                    if self.config.pretty {
4456                        self.write_newline();
4457                        self.write_indent();
4458                    } else {
4459                        self.write_space();
4460                    }
4461                    self.write_keyword("LIMIT");
4462                    self.write_space();
4463                    self.write_limit_expr(&limit.this)?;
4464                    if limit.percent {
4465                        self.write_space();
4466                        self.write_keyword("PERCENT");
4467                    }
4468                    // Emit any comments that were captured from before the LIMIT keyword
4469                    for comment in &limit.comments {
4470                        self.write(" ");
4471                        self.write_formatted_comment(comment);
4472                    }
4473                }
4474            }
4475
4476            // Convert TOP to LIMIT for non-TOP dialects
4477            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4478                if let Some(top) = &select.top {
4479                    if !top.percent && !top.with_ties {
4480                        if self.config.pretty {
4481                            self.write_newline();
4482                            self.write_indent();
4483                        } else {
4484                            self.write_space();
4485                        }
4486                        self.write_keyword("LIMIT");
4487                        self.write_space();
4488                        self.generate_expression(&top.this)?;
4489                    }
4490                }
4491            }
4492
4493            // If FETCH will be converted to LIMIT and there's also OFFSET,
4494            // emit LIMIT from FETCH BEFORE the OFFSET
4495            if fetch_as_limit && select.offset.is_some() {
4496                if let Some(fetch) = &select.fetch {
4497                    if self.config.pretty {
4498                        self.write_newline();
4499                        self.write_indent();
4500                    } else {
4501                        self.write_space();
4502                    }
4503                    self.write_keyword("LIMIT");
4504                    self.write_space();
4505                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4506                }
4507            }
4508
4509            // OFFSET
4510            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4511            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4512            if let Some(offset) = &select.offset {
4513                if self.config.pretty {
4514                    self.write_newline();
4515                    self.write_indent();
4516                } else {
4517                    self.write_space();
4518                }
4519                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4520                    // SQL Server 2012+ OFFSET ... FETCH syntax
4521                    self.write_keyword("OFFSET");
4522                    self.write_space();
4523                    self.write_limit_expr(&offset.this)?;
4524                    self.write_space();
4525                    self.write_keyword("ROWS");
4526                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4527                    if let Some(limit) = &select.limit {
4528                        self.write_space();
4529                        self.write_keyword("FETCH NEXT");
4530                        self.write_space();
4531                        self.write_limit_expr(&limit.this)?;
4532                        self.write_space();
4533                        self.write_keyword("ROWS ONLY");
4534                    }
4535                } else {
4536                    self.write_keyword("OFFSET");
4537                    self.write_space();
4538                    self.write_limit_expr(&offset.this)?;
4539                    // Output ROWS keyword if it was in the original SQL
4540                    if offset.rows == Some(true) {
4541                        self.write_space();
4542                        self.write_keyword("ROWS");
4543                    }
4544                }
4545            }
4546        }
4547
4548        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4549        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4550            if let Some(limit_by) = &select.limit_by {
4551                if !limit_by.is_empty() {
4552                    self.write_space();
4553                    self.write_keyword("BY");
4554                    self.write_space();
4555                    for (i, expr) in limit_by.iter().enumerate() {
4556                        if i > 0 {
4557                            self.write(", ");
4558                        }
4559                        self.generate_expression(expr)?;
4560                    }
4561                }
4562            }
4563        }
4564
4565        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4566        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4567            if let Some(settings) = &select.settings {
4568                if self.config.pretty {
4569                    self.write_newline();
4570                    self.write_indent();
4571                } else {
4572                    self.write_space();
4573                }
4574                self.write_keyword("SETTINGS");
4575                self.write_space();
4576                for (i, expr) in settings.iter().enumerate() {
4577                    if i > 0 {
4578                        self.write(", ");
4579                    }
4580                    self.generate_expression(expr)?;
4581                }
4582            }
4583
4584            if let Some(format_expr) = &select.format {
4585                if self.config.pretty {
4586                    self.write_newline();
4587                    self.write_indent();
4588                } else {
4589                    self.write_space();
4590                }
4591                self.write_keyword("FORMAT");
4592                self.write_space();
4593                self.generate_expression(format_expr)?;
4594            }
4595        }
4596
4597        // FETCH FIRST/NEXT
4598        if let Some(fetch) = &select.fetch {
4599            // Check if we already emitted LIMIT from FETCH before OFFSET
4600            let fetch_already_as_limit = select.offset.is_some()
4601                && !fetch.percent
4602                && !fetch.with_ties
4603                && fetch.count.is_some()
4604                && matches!(
4605                    self.config.dialect,
4606                    Some(DialectType::Spark)
4607                        | Some(DialectType::Hive)
4608                        | Some(DialectType::DuckDB)
4609                        | Some(DialectType::SQLite)
4610                        | Some(DialectType::MySQL)
4611                        | Some(DialectType::BigQuery)
4612                        | Some(DialectType::Databricks)
4613                        | Some(DialectType::StarRocks)
4614                        | Some(DialectType::Doris)
4615                        | Some(DialectType::Athena)
4616                        | Some(DialectType::ClickHouse)
4617                        | Some(DialectType::Redshift)
4618                );
4619
4620            if fetch_already_as_limit {
4621                // Already emitted as LIMIT before OFFSET, skip
4622            } else {
4623                if self.config.pretty {
4624                    self.write_newline();
4625                    self.write_indent();
4626                } else {
4627                    self.write_space();
4628                }
4629
4630                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4631                let use_limit = !fetch.percent
4632                    && !fetch.with_ties
4633                    && fetch.count.is_some()
4634                    && matches!(
4635                        self.config.dialect,
4636                        Some(DialectType::Spark)
4637                            | Some(DialectType::Hive)
4638                            | Some(DialectType::DuckDB)
4639                            | Some(DialectType::SQLite)
4640                            | Some(DialectType::MySQL)
4641                            | Some(DialectType::BigQuery)
4642                            | Some(DialectType::Databricks)
4643                            | Some(DialectType::StarRocks)
4644                            | Some(DialectType::Doris)
4645                            | Some(DialectType::Athena)
4646                            | Some(DialectType::ClickHouse)
4647                            | Some(DialectType::Redshift)
4648                    );
4649
4650                if use_limit {
4651                    self.write_keyword("LIMIT");
4652                    self.write_space();
4653                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4654                } else {
4655                    self.write_keyword("FETCH");
4656                    self.write_space();
4657                    self.write_keyword(&fetch.direction);
4658                    if let Some(ref count) = fetch.count {
4659                        self.write_space();
4660                        self.generate_expression(count)?;
4661                    }
4662                    if fetch.percent {
4663                        self.write_space();
4664                        self.write_keyword("PERCENT");
4665                    }
4666                    if fetch.rows {
4667                        self.write_space();
4668                        self.write_keyword("ROWS");
4669                    }
4670                    if fetch.with_ties {
4671                        self.write_space();
4672                        self.write_keyword("WITH TIES");
4673                    } else {
4674                        self.write_space();
4675                        self.write_keyword("ONLY");
4676                    }
4677                }
4678            } // close fetch_already_as_limit else
4679        }
4680
4681        // SAMPLE / TABLESAMPLE
4682        if let Some(sample) = &select.sample {
4683            use crate::dialects::DialectType;
4684            if self.config.pretty {
4685                self.write_newline();
4686            } else {
4687                self.write_space();
4688            }
4689
4690            if sample.is_using_sample {
4691                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4692                self.write_keyword("USING SAMPLE");
4693                self.generate_sample_body(sample)?;
4694            } else {
4695                self.write_keyword("TABLESAMPLE");
4696
4697                // Snowflake defaults to BERNOULLI when no explicit method is given
4698                let snowflake_bernoulli =
4699                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4700                        && !sample.explicit_method;
4701                if snowflake_bernoulli {
4702                    self.write_space();
4703                    self.write_keyword("BERNOULLI");
4704                }
4705
4706                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4707                if matches!(sample.method, SampleMethod::Bucket) {
4708                    self.write_space();
4709                    self.write("(");
4710                    self.write_keyword("BUCKET");
4711                    self.write_space();
4712                    if let Some(ref num) = sample.bucket_numerator {
4713                        self.generate_expression(num)?;
4714                    }
4715                    self.write_space();
4716                    self.write_keyword("OUT OF");
4717                    self.write_space();
4718                    if let Some(ref denom) = sample.bucket_denominator {
4719                        self.generate_expression(denom)?;
4720                    }
4721                    if let Some(ref field) = sample.bucket_field {
4722                        self.write_space();
4723                        self.write_keyword("ON");
4724                        self.write_space();
4725                        self.generate_expression(field)?;
4726                    }
4727                    self.write(")");
4728                } else if sample.unit_after_size {
4729                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4730                    if sample.explicit_method && sample.method_before_size {
4731                        self.write_space();
4732                        match sample.method {
4733                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4734                            SampleMethod::System => self.write_keyword("SYSTEM"),
4735                            SampleMethod::Block => self.write_keyword("BLOCK"),
4736                            SampleMethod::Row => self.write_keyword("ROW"),
4737                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4738                            _ => {}
4739                        }
4740                    }
4741                    self.write(" (");
4742                    self.generate_expression(&sample.size)?;
4743                    self.write_space();
4744                    match sample.method {
4745                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4746                        SampleMethod::Row => self.write_keyword("ROWS"),
4747                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4748                        _ => {
4749                            self.write_keyword("PERCENT");
4750                        }
4751                    }
4752                    self.write(")");
4753                } else {
4754                    // Syntax: TABLESAMPLE METHOD (size)
4755                    self.write_space();
4756                    match sample.method {
4757                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4758                        SampleMethod::System => self.write_keyword("SYSTEM"),
4759                        SampleMethod::Block => self.write_keyword("BLOCK"),
4760                        SampleMethod::Row => self.write_keyword("ROW"),
4761                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4762                        SampleMethod::Bucket => {}
4763                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4764                    }
4765                    self.write(" (");
4766                    self.generate_expression(&sample.size)?;
4767                    if matches!(sample.method, SampleMethod::Percent) {
4768                        self.write_space();
4769                        self.write_keyword("PERCENT");
4770                    }
4771                    self.write(")");
4772                }
4773            }
4774
4775            if let Some(seed) = &sample.seed {
4776                self.write_space();
4777                // Databricks/Spark use REPEATABLE, not SEED
4778                let use_seed = sample.use_seed_keyword
4779                    && !matches!(
4780                        self.config.dialect,
4781                        Some(crate::dialects::DialectType::Databricks)
4782                            | Some(crate::dialects::DialectType::Spark)
4783                    );
4784                if use_seed {
4785                    self.write_keyword("SEED");
4786                } else {
4787                    self.write_keyword("REPEATABLE");
4788                }
4789                self.write(" (");
4790                self.generate_expression(seed)?;
4791                self.write(")");
4792            }
4793        }
4794
4795        // FOR UPDATE/SHARE locks
4796        // Skip locking clauses for dialects that don't support them
4797        if self.config.locking_reads_supported {
4798            for lock in &select.locks {
4799                if self.config.pretty {
4800                    self.write_newline();
4801                    self.write_indent();
4802                } else {
4803                    self.write_space();
4804                }
4805                self.generate_lock(lock)?;
4806            }
4807        }
4808
4809        // FOR XML clause (T-SQL)
4810        if !select.for_xml.is_empty() {
4811            if self.config.pretty {
4812                self.write_newline();
4813                self.write_indent();
4814            } else {
4815                self.write_space();
4816            }
4817            self.write_keyword("FOR XML");
4818            for (i, opt) in select.for_xml.iter().enumerate() {
4819                if self.config.pretty {
4820                    if i > 0 {
4821                        self.write(",");
4822                    }
4823                    self.write_newline();
4824                    self.write_indent();
4825                    self.write("  "); // extra indent for options
4826                } else {
4827                    if i > 0 {
4828                        self.write(",");
4829                    }
4830                    self.write_space();
4831                }
4832                self.generate_for_xml_option(opt)?;
4833            }
4834        }
4835
4836        // TSQL: OPTION clause
4837        if let Some(ref option) = select.option {
4838            if matches!(
4839                self.config.dialect,
4840                Some(crate::dialects::DialectType::TSQL)
4841                    | Some(crate::dialects::DialectType::Fabric)
4842            ) {
4843                self.write_space();
4844                self.write(option);
4845            }
4846        }
4847
4848        Ok(())
4849    }
4850
4851    /// Generate a single FOR XML option
4852    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4853        match opt {
4854            Expression::QueryOption(qo) => {
4855                // Extract the option name from Var
4856                if let Expression::Var(var) = &*qo.this {
4857                    self.write(&var.this);
4858                } else {
4859                    self.generate_expression(&qo.this)?;
4860                }
4861                // If there's an expression (like PATH('element')), output it in parens
4862                if let Some(expr) = &qo.expression {
4863                    self.write("(");
4864                    self.generate_expression(expr)?;
4865                    self.write(")");
4866                }
4867            }
4868            _ => {
4869                self.generate_expression(opt)?;
4870            }
4871        }
4872        Ok(())
4873    }
4874
4875    fn generate_with(&mut self, with: &With) -> Result<()> {
4876        use crate::dialects::DialectType;
4877
4878        // Output leading comments before WITH
4879        for comment in &with.leading_comments {
4880            self.write_formatted_comment(comment);
4881            self.write(" ");
4882        }
4883        self.write_keyword("WITH");
4884        if with.recursive && self.config.cte_recursive_keyword_required {
4885            self.write_space();
4886            self.write_keyword("RECURSIVE");
4887        }
4888        self.write_space();
4889
4890        // BigQuery doesn't support column aliases in CTE definitions
4891        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4892
4893        for (i, cte) in with.ctes.iter().enumerate() {
4894            if i > 0 {
4895                self.write(",");
4896                if self.config.pretty {
4897                    self.write_space();
4898                } else {
4899                    self.write(" ");
4900                }
4901            }
4902            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4903                self.generate_expression(&cte.this)?;
4904                self.write_space();
4905                self.write_keyword("AS");
4906                self.write_space();
4907                self.generate_identifier(&cte.alias)?;
4908                continue;
4909            }
4910            self.generate_identifier(&cte.alias)?;
4911            // Output CTE comments after alias name, before AS
4912            for comment in &cte.comments {
4913                self.write_space();
4914                self.write_formatted_comment(comment);
4915            }
4916            if !cte.columns.is_empty() && !skip_cte_columns {
4917                self.write("(");
4918                for (j, col) in cte.columns.iter().enumerate() {
4919                    if j > 0 {
4920                        self.write(", ");
4921                    }
4922                    self.generate_identifier(col)?;
4923                }
4924                self.write(")");
4925            }
4926            // USING KEY (columns) for DuckDB recursive CTEs
4927            if !cte.key_expressions.is_empty() {
4928                self.write_space();
4929                self.write_keyword("USING KEY");
4930                self.write(" (");
4931                for (i, key) in cte.key_expressions.iter().enumerate() {
4932                    if i > 0 {
4933                        self.write(", ");
4934                    }
4935                    self.generate_identifier(key)?;
4936                }
4937                self.write(")");
4938            }
4939            self.write_space();
4940            self.write_keyword("AS");
4941            // MATERIALIZED / NOT MATERIALIZED
4942            if let Some(materialized) = cte.materialized {
4943                self.write_space();
4944                if materialized {
4945                    self.write_keyword("MATERIALIZED");
4946                } else {
4947                    self.write_keyword("NOT MATERIALIZED");
4948                }
4949            }
4950            self.write(" (");
4951            if self.config.pretty {
4952                self.write_newline();
4953                self.indent_level += 1;
4954                self.write_indent();
4955            }
4956            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4957            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4958            let wrap_values_in_select = matches!(
4959                self.config.dialect,
4960                Some(DialectType::Spark) | Some(DialectType::Databricks)
4961            ) && matches!(&cte.this, Expression::Values(_));
4962
4963            if wrap_values_in_select {
4964                self.write_keyword("SELECT");
4965                self.write(" * ");
4966                self.write_keyword("FROM");
4967                self.write_space();
4968            }
4969            self.generate_expression(&cte.this)?;
4970            if self.config.pretty {
4971                self.write_newline();
4972                self.indent_level -= 1;
4973                self.write_indent();
4974            }
4975            self.write(")");
4976        }
4977
4978        // Generate SEARCH/CYCLE clause if present
4979        if let Some(search) = &with.search {
4980            self.write_space();
4981            self.generate_expression(search)?;
4982        }
4983
4984        Ok(())
4985    }
4986
4987    /// Generate joins with proper nesting structure for pretty printing.
4988    /// Deferred-condition joins "own" the non-deferred joins that follow them
4989    /// within the same nesting_group.
4990    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4991        let mut i = 0;
4992        while i < joins.len() {
4993            if joins[i].deferred_condition {
4994                let parent_group = joins[i].nesting_group;
4995
4996                // This join owns the following non-deferred joins in the same nesting_group
4997                // First output the join keyword and table (without condition)
4998                self.generate_join_without_condition(&joins[i])?;
4999
5000                // Find the range of child joins: same nesting_group and not deferred
5001                let child_start = i + 1;
5002                let mut child_end = child_start;
5003                while child_end < joins.len()
5004                    && !joins[child_end].deferred_condition
5005                    && joins[child_end].nesting_group == parent_group
5006                {
5007                    child_end += 1;
5008                }
5009
5010                // Output child joins with extra indentation
5011                if child_start < child_end {
5012                    self.indent_level += 1;
5013                    for j in child_start..child_end {
5014                        self.generate_join(&joins[j])?;
5015                    }
5016                    self.indent_level -= 1;
5017                }
5018
5019                // Output the deferred condition at the parent level
5020                self.generate_join_condition(&joins[i])?;
5021
5022                i = child_end;
5023            } else {
5024                // Regular join (no nesting)
5025                self.generate_join(&joins[i])?;
5026                i += 1;
5027            }
5028        }
5029        Ok(())
5030    }
5031
5032    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5033    /// Used for deferred-condition joins where the condition is output after child joins.
5034    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5035        // Save and temporarily clear the condition to prevent generate_join from outputting it
5036        // We achieve this by creating a modified copy
5037        let mut join_copy = join.clone();
5038        join_copy.on = None;
5039        join_copy.using = Vec::new();
5040        join_copy.deferred_condition = false;
5041        self.generate_join(&join_copy)
5042    }
5043
5044    fn generate_join(&mut self, join: &Join) -> Result<()> {
5045        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5046        if join.kind == JoinKind::Implicit {
5047            self.write(",");
5048            if self.config.pretty {
5049                self.write_newline();
5050                self.write_indent();
5051            } else {
5052                self.write_space();
5053            }
5054            self.generate_expression(&join.this)?;
5055            return Ok(());
5056        }
5057
5058        if self.config.pretty {
5059            self.write_newline();
5060            self.write_indent();
5061        } else {
5062            self.write_space();
5063        }
5064
5065        // Helper: format hint suffix (e.g., " LOOP" or "")
5066        // Only include join hints for dialects that support them
5067        let hint_str = if self.config.join_hints {
5068            join.join_hint
5069                .as_ref()
5070                .map(|h| format!(" {}", h))
5071                .unwrap_or_default()
5072        } else {
5073            String::new()
5074        };
5075
5076        let clickhouse_join_keyword =
5077            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5078                if let Some(hint) = &join.join_hint {
5079                    let mut global = false;
5080                    let mut strictness: Option<&'static str> = None;
5081                    for part in hint.split_whitespace() {
5082                        match part.to_uppercase().as_str() {
5083                            "GLOBAL" => global = true,
5084                            "ANY" => strictness = Some("ANY"),
5085                            "ASOF" => strictness = Some("ASOF"),
5086                            "SEMI" => strictness = Some("SEMI"),
5087                            "ANTI" => strictness = Some("ANTI"),
5088                            _ => {}
5089                        }
5090                    }
5091
5092                    if global || strictness.is_some() {
5093                        let join_type = match join.kind {
5094                            JoinKind::Left => {
5095                                if join.use_outer_keyword {
5096                                    "LEFT OUTER"
5097                                } else if join.use_inner_keyword {
5098                                    "LEFT INNER"
5099                                } else {
5100                                    "LEFT"
5101                                }
5102                            }
5103                            JoinKind::Right => {
5104                                if join.use_outer_keyword {
5105                                    "RIGHT OUTER"
5106                                } else if join.use_inner_keyword {
5107                                    "RIGHT INNER"
5108                                } else {
5109                                    "RIGHT"
5110                                }
5111                            }
5112                            JoinKind::Full => {
5113                                if join.use_outer_keyword {
5114                                    "FULL OUTER"
5115                                } else {
5116                                    "FULL"
5117                                }
5118                            }
5119                            JoinKind::Inner => {
5120                                if join.use_inner_keyword {
5121                                    "INNER"
5122                                } else {
5123                                    ""
5124                                }
5125                            }
5126                            _ => "",
5127                        };
5128
5129                        let mut parts = Vec::new();
5130                        if global {
5131                            parts.push("GLOBAL");
5132                        }
5133                        if !join_type.is_empty() {
5134                            parts.push(join_type);
5135                        }
5136                        if let Some(strict) = strictness {
5137                            parts.push(strict);
5138                        }
5139                        parts.push("JOIN");
5140                        Some(parts.join(" "))
5141                    } else {
5142                        None
5143                    }
5144                } else {
5145                    None
5146                }
5147            } else {
5148                None
5149            };
5150
5151        // Output any comments associated with this join
5152        // In pretty mode, comments go on their own line before the join keyword
5153        // In non-pretty mode, comments go inline before the join keyword
5154        if !join.comments.is_empty() {
5155            if self.config.pretty {
5156                // In pretty mode, go back before the newline+indent we just wrote
5157                // and output comments on their own lines
5158                // We need to output comments BEFORE the join keyword on separate lines
5159                // Trim the trailing newline+indent we already wrote
5160                let trimmed = self.output.trim_end().len();
5161                self.output.truncate(trimmed);
5162                for comment in &join.comments {
5163                    self.write_newline();
5164                    self.write_indent();
5165                    self.write_formatted_comment(comment);
5166                }
5167                self.write_newline();
5168                self.write_indent();
5169            } else {
5170                for comment in &join.comments {
5171                    self.write_formatted_comment(comment);
5172                    self.write_space();
5173                }
5174            }
5175        }
5176
5177        let directed_str = if join.directed { " DIRECTED" } else { "" };
5178
5179        if let Some(keyword) = clickhouse_join_keyword {
5180            self.write_keyword(&keyword);
5181        } else {
5182            match join.kind {
5183                JoinKind::Inner => {
5184                    if join.use_inner_keyword {
5185                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5186                    } else {
5187                        self.write_keyword(&format!(
5188                            "{}{}JOIN",
5189                            if hint_str.is_empty() {
5190                                String::new()
5191                            } else {
5192                                format!("{} ", hint_str.trim())
5193                            },
5194                            if directed_str.is_empty() {
5195                                ""
5196                            } else {
5197                                "DIRECTED "
5198                            }
5199                        ));
5200                    }
5201                }
5202                JoinKind::Left => {
5203                    if join.use_outer_keyword {
5204                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5205                    } else if join.use_inner_keyword {
5206                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5207                    } else {
5208                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5209                    }
5210                }
5211                JoinKind::Right => {
5212                    if join.use_outer_keyword {
5213                        self.write_keyword(&format!(
5214                            "RIGHT OUTER{}{} JOIN",
5215                            hint_str, directed_str
5216                        ));
5217                    } else if join.use_inner_keyword {
5218                        self.write_keyword(&format!(
5219                            "RIGHT INNER{}{} JOIN",
5220                            hint_str, directed_str
5221                        ));
5222                    } else {
5223                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5224                    }
5225                }
5226                JoinKind::Full => {
5227                    if join.use_outer_keyword {
5228                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5229                    } else {
5230                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5231                    }
5232                }
5233                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5234                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5235                JoinKind::Natural => {
5236                    if join.use_inner_keyword {
5237                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5238                    } else {
5239                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5240                    }
5241                }
5242                JoinKind::NaturalLeft => {
5243                    if join.use_outer_keyword {
5244                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5245                    } else {
5246                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5247                    }
5248                }
5249                JoinKind::NaturalRight => {
5250                    if join.use_outer_keyword {
5251                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5252                    } else {
5253                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5254                    }
5255                }
5256                JoinKind::NaturalFull => {
5257                    if join.use_outer_keyword {
5258                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5259                    } else {
5260                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5261                    }
5262                }
5263                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5264                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5265                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5266                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5267                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5268                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5269                JoinKind::CrossApply => {
5270                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5271                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5272                        self.write_keyword("CROSS APPLY");
5273                    } else {
5274                        self.write_keyword("INNER JOIN LATERAL");
5275                    }
5276                }
5277                JoinKind::OuterApply => {
5278                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5279                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5280                        self.write_keyword("OUTER APPLY");
5281                    } else {
5282                        self.write_keyword("LEFT JOIN LATERAL");
5283                    }
5284                }
5285                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5286                JoinKind::AsOfLeft => {
5287                    if join.use_outer_keyword {
5288                        self.write_keyword("ASOF LEFT OUTER JOIN");
5289                    } else {
5290                        self.write_keyword("ASOF LEFT JOIN");
5291                    }
5292                }
5293                JoinKind::AsOfRight => {
5294                    if join.use_outer_keyword {
5295                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5296                    } else {
5297                        self.write_keyword("ASOF RIGHT JOIN");
5298                    }
5299                }
5300                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5301                JoinKind::LeftLateral => {
5302                    if join.use_outer_keyword {
5303                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5304                    } else {
5305                        self.write_keyword("LEFT LATERAL JOIN");
5306                    }
5307                }
5308                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5309                JoinKind::Implicit => {
5310                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5311                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5312                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5313                    use crate::dialects::DialectType;
5314                    let is_cj_dialect = matches!(
5315                        self.config.dialect,
5316                        Some(DialectType::BigQuery)
5317                            | Some(DialectType::Hive)
5318                            | Some(DialectType::Spark)
5319                            | Some(DialectType::Databricks)
5320                    );
5321                    let source_is_same = self.config.source_dialect.is_some()
5322                        && self.config.source_dialect == self.config.dialect;
5323                    let source_is_cj = matches!(
5324                        self.config.source_dialect,
5325                        Some(DialectType::BigQuery)
5326                            | Some(DialectType::Hive)
5327                            | Some(DialectType::Spark)
5328                            | Some(DialectType::Databricks)
5329                    );
5330                    if is_cj_dialect
5331                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5332                    {
5333                        self.write_keyword("CROSS JOIN");
5334                    } else {
5335                        // Implicit join uses comma: FROM a, b
5336                        // We already wrote a space before the match, so replace with comma
5337                        // by removing trailing space and writing ", "
5338                        self.output.truncate(self.output.trim_end().len());
5339                        self.write(",");
5340                    }
5341                }
5342                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5343                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5344                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5345            }
5346        }
5347
5348        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5349        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5350            self.write_space();
5351            match &join.this {
5352                Expression::Tuple(t) => {
5353                    for (i, item) in t.expressions.iter().enumerate() {
5354                        if i > 0 {
5355                            self.write(", ");
5356                        }
5357                        self.generate_expression(item)?;
5358                    }
5359                }
5360                other => {
5361                    self.generate_expression(other)?;
5362                }
5363            }
5364        } else {
5365            self.write_space();
5366            self.generate_expression(&join.this)?;
5367        }
5368
5369        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5370        if !join.deferred_condition {
5371            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5372            if let Some(match_cond) = &join.match_condition {
5373                self.write_space();
5374                self.write_keyword("MATCH_CONDITION");
5375                self.write(" (");
5376                self.generate_expression(match_cond)?;
5377                self.write(")");
5378            }
5379
5380            if let Some(on) = &join.on {
5381                if self.config.pretty {
5382                    self.write_newline();
5383                    self.indent_level += 1;
5384                    self.write_indent();
5385                    self.write_keyword("ON");
5386                    self.write_space();
5387                    self.generate_join_on_condition(on)?;
5388                    self.indent_level -= 1;
5389                } else {
5390                    self.write_space();
5391                    self.write_keyword("ON");
5392                    self.write_space();
5393                    self.generate_expression(on)?;
5394                }
5395            }
5396
5397            if !join.using.is_empty() {
5398                if self.config.pretty {
5399                    self.write_newline();
5400                    self.indent_level += 1;
5401                    self.write_indent();
5402                    self.write_keyword("USING");
5403                    self.write(" (");
5404                    for (i, col) in join.using.iter().enumerate() {
5405                        if i > 0 {
5406                            self.write(", ");
5407                        }
5408                        self.generate_identifier(col)?;
5409                    }
5410                    self.write(")");
5411                    self.indent_level -= 1;
5412                } else {
5413                    self.write_space();
5414                    self.write_keyword("USING");
5415                    self.write(" (");
5416                    for (i, col) in join.using.iter().enumerate() {
5417                        if i > 0 {
5418                            self.write(", ");
5419                        }
5420                        self.generate_identifier(col)?;
5421                    }
5422                    self.write(")");
5423                }
5424            }
5425        }
5426
5427        // Generate PIVOT/UNPIVOT expressions that follow this join
5428        for pivot in &join.pivots {
5429            self.write_space();
5430            self.generate_expression(pivot)?;
5431        }
5432
5433        Ok(())
5434    }
5435
5436    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5437    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5438        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5439        if let Some(match_cond) = &join.match_condition {
5440            self.write_space();
5441            self.write_keyword("MATCH_CONDITION");
5442            self.write(" (");
5443            self.generate_expression(match_cond)?;
5444            self.write(")");
5445        }
5446
5447        if let Some(on) = &join.on {
5448            if self.config.pretty {
5449                self.write_newline();
5450                self.indent_level += 1;
5451                self.write_indent();
5452                self.write_keyword("ON");
5453                self.write_space();
5454                // In pretty mode, split AND conditions onto separate lines
5455                self.generate_join_on_condition(on)?;
5456                self.indent_level -= 1;
5457            } else {
5458                self.write_space();
5459                self.write_keyword("ON");
5460                self.write_space();
5461                self.generate_expression(on)?;
5462            }
5463        }
5464
5465        if !join.using.is_empty() {
5466            if self.config.pretty {
5467                self.write_newline();
5468                self.indent_level += 1;
5469                self.write_indent();
5470                self.write_keyword("USING");
5471                self.write(" (");
5472                for (i, col) in join.using.iter().enumerate() {
5473                    if i > 0 {
5474                        self.write(", ");
5475                    }
5476                    self.generate_identifier(col)?;
5477                }
5478                self.write(")");
5479                self.indent_level -= 1;
5480            } else {
5481                self.write_space();
5482                self.write_keyword("USING");
5483                self.write(" (");
5484                for (i, col) in join.using.iter().enumerate() {
5485                    if i > 0 {
5486                        self.write(", ");
5487                    }
5488                    self.generate_identifier(col)?;
5489                }
5490                self.write(")");
5491            }
5492        }
5493
5494        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5495        for pivot in &join.pivots {
5496            self.write_space();
5497            self.generate_expression(pivot)?;
5498        }
5499
5500        Ok(())
5501    }
5502
5503    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5504    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5505        if let Expression::And(and_op) = expr {
5506            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5507                self.generate_expression(conditions[0])?;
5508                for condition in conditions.iter().skip(1) {
5509                    self.write_newline();
5510                    self.write_indent();
5511                    self.write_keyword("AND");
5512                    self.write_space();
5513                    self.generate_expression(condition)?;
5514                }
5515                return Ok(());
5516            }
5517        }
5518
5519        self.generate_expression(expr)
5520    }
5521
5522    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5523        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5524        self.write("(");
5525        self.generate_expression(&jt.left)?;
5526
5527        // Generate all joins
5528        for join in &jt.joins {
5529            self.generate_join(join)?;
5530        }
5531
5532        // Generate LATERAL VIEW clauses (Hive/Spark)
5533        for lv in &jt.lateral_views {
5534            self.generate_lateral_view(lv)?;
5535        }
5536
5537        self.write(")");
5538
5539        // Alias
5540        if let Some(alias) = &jt.alias {
5541            self.write_space();
5542            self.write_keyword("AS");
5543            self.write_space();
5544            self.generate_identifier(alias)?;
5545        }
5546
5547        Ok(())
5548    }
5549
5550    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5551        use crate::dialects::DialectType;
5552
5553        if self.config.pretty {
5554            self.write_newline();
5555            self.write_indent();
5556        } else {
5557            self.write_space();
5558        }
5559
5560        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5561        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5562        let use_lateral_join = matches!(
5563            self.config.dialect,
5564            Some(DialectType::PostgreSQL)
5565                | Some(DialectType::DuckDB)
5566                | Some(DialectType::Snowflake)
5567                | Some(DialectType::TSQL)
5568                | Some(DialectType::Presto)
5569                | Some(DialectType::Trino)
5570                | Some(DialectType::Athena)
5571        );
5572
5573        // Check if target dialect should use UNNEST instead of EXPLODE
5574        let use_unnest = matches!(
5575            self.config.dialect,
5576            Some(DialectType::DuckDB)
5577                | Some(DialectType::Presto)
5578                | Some(DialectType::Trino)
5579                | Some(DialectType::Athena)
5580        );
5581
5582        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5583        let (is_posexplode, func_args) = match &lv.this {
5584            Expression::Explode(uf) => {
5585                // Expression::Explode is the dedicated EXPLODE expression type
5586                (false, vec![uf.this.clone()])
5587            }
5588            Expression::Unnest(uf) => {
5589                let mut args = vec![uf.this.clone()];
5590                args.extend(uf.expressions.clone());
5591                (false, args)
5592            }
5593            Expression::Function(func) => {
5594                let name = func.name.to_uppercase();
5595                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5596                    (true, func.args.clone())
5597                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5598                    (false, func.args.clone())
5599                } else {
5600                    (false, vec![])
5601                }
5602            }
5603            _ => (false, vec![]),
5604        };
5605
5606        if use_lateral_join {
5607            // Convert to CROSS JOIN for PostgreSQL-like dialects
5608            if lv.outer {
5609                self.write_keyword("LEFT JOIN LATERAL");
5610            } else {
5611                self.write_keyword("CROSS JOIN");
5612            }
5613            self.write_space();
5614
5615            if use_unnest && !func_args.is_empty() {
5616                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5617                // For DuckDB, also convert ARRAY(y) -> [y]
5618                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5619                    // DuckDB: ARRAY(y) -> [y]
5620                    func_args
5621                        .iter()
5622                        .map(|a| {
5623                            if let Expression::Function(ref f) = a {
5624                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5625                                    return Expression::ArrayFunc(Box::new(
5626                                        crate::expressions::ArrayConstructor {
5627                                            expressions: f.args.clone(),
5628                                            bracket_notation: true,
5629                                            use_list_keyword: false,
5630                                        },
5631                                    ));
5632                                }
5633                            }
5634                            a.clone()
5635                        })
5636                        .collect::<Vec<_>>()
5637                } else if matches!(
5638                    self.config.dialect,
5639                    Some(DialectType::Presto)
5640                        | Some(DialectType::Trino)
5641                        | Some(DialectType::Athena)
5642                ) {
5643                    // Presto: ARRAY(y) -> ARRAY[y]
5644                    func_args
5645                        .iter()
5646                        .map(|a| {
5647                            if let Expression::Function(ref f) = a {
5648                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5649                                    return Expression::ArrayFunc(Box::new(
5650                                        crate::expressions::ArrayConstructor {
5651                                            expressions: f.args.clone(),
5652                                            bracket_notation: true,
5653                                            use_list_keyword: false,
5654                                        },
5655                                    ));
5656                                }
5657                            }
5658                            a.clone()
5659                        })
5660                        .collect::<Vec<_>>()
5661                } else {
5662                    func_args
5663                };
5664
5665                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5666                if is_posexplode {
5667                    self.write_keyword("LATERAL");
5668                    self.write(" (");
5669                    self.write_keyword("SELECT");
5670                    self.write_space();
5671
5672                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5673                    // column_aliases[0] is the position column, rest are data columns
5674                    let pos_alias = if !lv.column_aliases.is_empty() {
5675                        lv.column_aliases[0].clone()
5676                    } else {
5677                        Identifier::new("pos")
5678                    };
5679                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5680                        lv.column_aliases[1..].to_vec()
5681                    } else {
5682                        vec![Identifier::new("col")]
5683                    };
5684
5685                    // pos - 1 AS pos
5686                    self.generate_identifier(&pos_alias)?;
5687                    self.write(" - 1");
5688                    self.write_space();
5689                    self.write_keyword("AS");
5690                    self.write_space();
5691                    self.generate_identifier(&pos_alias)?;
5692
5693                    // , col [, key, value ...]
5694                    for data_col in &data_aliases {
5695                        self.write(", ");
5696                        self.generate_identifier(data_col)?;
5697                    }
5698
5699                    self.write_space();
5700                    self.write_keyword("FROM");
5701                    self.write_space();
5702                    self.write_keyword("UNNEST");
5703                    self.write("(");
5704                    for (i, arg) in unnest_args.iter().enumerate() {
5705                        if i > 0 {
5706                            self.write(", ");
5707                        }
5708                        self.generate_expression(arg)?;
5709                    }
5710                    self.write(")");
5711                    self.write_space();
5712                    self.write_keyword("WITH ORDINALITY");
5713                    self.write_space();
5714                    self.write_keyword("AS");
5715                    self.write_space();
5716
5717                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5718                    let table_alias_ident = lv
5719                        .table_alias
5720                        .clone()
5721                        .unwrap_or_else(|| Identifier::new("t"));
5722                    self.generate_identifier(&table_alias_ident)?;
5723                    self.write("(");
5724                    for (i, data_col) in data_aliases.iter().enumerate() {
5725                        if i > 0 {
5726                            self.write(", ");
5727                        }
5728                        self.generate_identifier(data_col)?;
5729                    }
5730                    self.write(", ");
5731                    self.generate_identifier(&pos_alias)?;
5732                    self.write("))");
5733                } else {
5734                    self.write_keyword("UNNEST");
5735                    self.write("(");
5736                    for (i, arg) in unnest_args.iter().enumerate() {
5737                        if i > 0 {
5738                            self.write(", ");
5739                        }
5740                        self.generate_expression(arg)?;
5741                    }
5742                    self.write(")");
5743
5744                    // Add table and column aliases for non-POSEXPLODE
5745                    if let Some(alias) = &lv.table_alias {
5746                        self.write_space();
5747                        self.write_keyword("AS");
5748                        self.write_space();
5749                        self.generate_identifier(alias)?;
5750                        if !lv.column_aliases.is_empty() {
5751                            self.write("(");
5752                            for (i, col) in lv.column_aliases.iter().enumerate() {
5753                                if i > 0 {
5754                                    self.write(", ");
5755                                }
5756                                self.generate_identifier(col)?;
5757                            }
5758                            self.write(")");
5759                        }
5760                    } else if !lv.column_aliases.is_empty() {
5761                        self.write_space();
5762                        self.write_keyword("AS");
5763                        self.write(" t(");
5764                        for (i, col) in lv.column_aliases.iter().enumerate() {
5765                            if i > 0 {
5766                                self.write(", ");
5767                            }
5768                            self.generate_identifier(col)?;
5769                        }
5770                        self.write(")");
5771                    }
5772                }
5773            } else {
5774                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5775                if !lv.outer {
5776                    self.write_keyword("LATERAL");
5777                    self.write_space();
5778                }
5779                self.generate_expression(&lv.this)?;
5780
5781                // Add table and column aliases
5782                if let Some(alias) = &lv.table_alias {
5783                    self.write_space();
5784                    self.write_keyword("AS");
5785                    self.write_space();
5786                    self.generate_identifier(alias)?;
5787                    if !lv.column_aliases.is_empty() {
5788                        self.write("(");
5789                        for (i, col) in lv.column_aliases.iter().enumerate() {
5790                            if i > 0 {
5791                                self.write(", ");
5792                            }
5793                            self.generate_identifier(col)?;
5794                        }
5795                        self.write(")");
5796                    }
5797                } else if !lv.column_aliases.is_empty() {
5798                    self.write_space();
5799                    self.write_keyword("AS");
5800                    self.write(" t(");
5801                    for (i, col) in lv.column_aliases.iter().enumerate() {
5802                        if i > 0 {
5803                            self.write(", ");
5804                        }
5805                        self.generate_identifier(col)?;
5806                    }
5807                    self.write(")");
5808                }
5809            }
5810
5811            // For LEFT JOIN LATERAL, need ON TRUE
5812            if lv.outer {
5813                self.write_space();
5814                self.write_keyword("ON TRUE");
5815            }
5816        } else {
5817            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5818            self.write_keyword("LATERAL VIEW");
5819            if lv.outer {
5820                self.write_space();
5821                self.write_keyword("OUTER");
5822            }
5823            if self.config.pretty {
5824                self.write_newline();
5825                self.write_indent();
5826            } else {
5827                self.write_space();
5828            }
5829            self.generate_expression(&lv.this)?;
5830
5831            // Table alias
5832            if let Some(alias) = &lv.table_alias {
5833                self.write_space();
5834                self.generate_identifier(alias)?;
5835            }
5836
5837            // Column aliases
5838            if !lv.column_aliases.is_empty() {
5839                self.write_space();
5840                self.write_keyword("AS");
5841                self.write_space();
5842                for (i, col) in lv.column_aliases.iter().enumerate() {
5843                    if i > 0 {
5844                        self.write(", ");
5845                    }
5846                    self.generate_identifier(col)?;
5847                }
5848            }
5849        }
5850
5851        Ok(())
5852    }
5853
5854    fn generate_union(&mut self, union: &Union) -> Result<()> {
5855        // WITH clause
5856        if let Some(with) = &union.with {
5857            self.generate_with(with)?;
5858            self.write_space();
5859        }
5860        self.generate_expression(&union.left)?;
5861        if self.config.pretty {
5862            self.write_newline();
5863            self.write_indent();
5864        } else {
5865            self.write_space();
5866        }
5867
5868        // BigQuery set operation modifiers: [side] [kind] UNION
5869        if let Some(side) = &union.side {
5870            self.write_keyword(side);
5871            self.write_space();
5872        }
5873        if let Some(kind) = &union.kind {
5874            self.write_keyword(kind);
5875            self.write_space();
5876        }
5877
5878        self.write_keyword("UNION");
5879        if union.all {
5880            self.write_space();
5881            self.write_keyword("ALL");
5882        } else if union.distinct {
5883            self.write_space();
5884            self.write_keyword("DISTINCT");
5885        }
5886
5887        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5888        // DuckDB: BY NAME
5889        if union.corresponding || union.by_name {
5890            self.write_space();
5891            self.write_keyword("BY NAME");
5892        }
5893        if !union.on_columns.is_empty() {
5894            self.write_space();
5895            self.write_keyword("ON");
5896            self.write(" (");
5897            for (i, col) in union.on_columns.iter().enumerate() {
5898                if i > 0 {
5899                    self.write(", ");
5900                }
5901                self.generate_expression(col)?;
5902            }
5903            self.write(")");
5904        }
5905
5906        if self.config.pretty {
5907            self.write_newline();
5908            self.write_indent();
5909        } else {
5910            self.write_space();
5911        }
5912        self.generate_expression(&union.right)?;
5913        // ORDER BY, LIMIT, OFFSET for the set operation
5914        if let Some(order_by) = &union.order_by {
5915            if self.config.pretty {
5916                self.write_newline();
5917            } else {
5918                self.write_space();
5919            }
5920            self.write_keyword("ORDER BY");
5921            self.write_space();
5922            for (i, ordered) in order_by.expressions.iter().enumerate() {
5923                if i > 0 {
5924                    self.write(", ");
5925                }
5926                self.generate_ordered(ordered)?;
5927            }
5928        }
5929        if let Some(limit) = &union.limit {
5930            if self.config.pretty {
5931                self.write_newline();
5932            } else {
5933                self.write_space();
5934            }
5935            self.write_keyword("LIMIT");
5936            self.write_space();
5937            self.generate_expression(limit)?;
5938        }
5939        if let Some(offset) = &union.offset {
5940            if self.config.pretty {
5941                self.write_newline();
5942            } else {
5943                self.write_space();
5944            }
5945            self.write_keyword("OFFSET");
5946            self.write_space();
5947            self.generate_expression(offset)?;
5948        }
5949        // DISTRIBUTE BY (Hive/Spark)
5950        if let Some(distribute_by) = &union.distribute_by {
5951            self.write_space();
5952            self.write_keyword("DISTRIBUTE BY");
5953            self.write_space();
5954            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5955                if i > 0 {
5956                    self.write(", ");
5957                }
5958                self.generate_expression(expr)?;
5959            }
5960        }
5961        // SORT BY (Hive/Spark)
5962        if let Some(sort_by) = &union.sort_by {
5963            self.write_space();
5964            self.write_keyword("SORT BY");
5965            self.write_space();
5966            for (i, ord) in sort_by.expressions.iter().enumerate() {
5967                if i > 0 {
5968                    self.write(", ");
5969                }
5970                self.generate_ordered(ord)?;
5971            }
5972        }
5973        // CLUSTER BY (Hive/Spark)
5974        if let Some(cluster_by) = &union.cluster_by {
5975            self.write_space();
5976            self.write_keyword("CLUSTER BY");
5977            self.write_space();
5978            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5979                if i > 0 {
5980                    self.write(", ");
5981                }
5982                self.generate_ordered(ord)?;
5983            }
5984        }
5985        Ok(())
5986    }
5987
5988    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5989        // WITH clause
5990        if let Some(with) = &intersect.with {
5991            self.generate_with(with)?;
5992            self.write_space();
5993        }
5994        self.generate_expression(&intersect.left)?;
5995        if self.config.pretty {
5996            self.write_newline();
5997            self.write_indent();
5998        } else {
5999            self.write_space();
6000        }
6001
6002        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6003        if let Some(side) = &intersect.side {
6004            self.write_keyword(side);
6005            self.write_space();
6006        }
6007        if let Some(kind) = &intersect.kind {
6008            self.write_keyword(kind);
6009            self.write_space();
6010        }
6011
6012        self.write_keyword("INTERSECT");
6013        if intersect.all {
6014            self.write_space();
6015            self.write_keyword("ALL");
6016        } else if intersect.distinct {
6017            self.write_space();
6018            self.write_keyword("DISTINCT");
6019        }
6020
6021        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6022        // DuckDB: BY NAME
6023        if intersect.corresponding || intersect.by_name {
6024            self.write_space();
6025            self.write_keyword("BY NAME");
6026        }
6027        if !intersect.on_columns.is_empty() {
6028            self.write_space();
6029            self.write_keyword("ON");
6030            self.write(" (");
6031            for (i, col) in intersect.on_columns.iter().enumerate() {
6032                if i > 0 {
6033                    self.write(", ");
6034                }
6035                self.generate_expression(col)?;
6036            }
6037            self.write(")");
6038        }
6039
6040        if self.config.pretty {
6041            self.write_newline();
6042            self.write_indent();
6043        } else {
6044            self.write_space();
6045        }
6046        self.generate_expression(&intersect.right)?;
6047        // ORDER BY, LIMIT, OFFSET for the set operation
6048        if let Some(order_by) = &intersect.order_by {
6049            if self.config.pretty {
6050                self.write_newline();
6051            } else {
6052                self.write_space();
6053            }
6054            self.write_keyword("ORDER BY");
6055            self.write_space();
6056            for (i, ordered) in order_by.expressions.iter().enumerate() {
6057                if i > 0 {
6058                    self.write(", ");
6059                }
6060                self.generate_ordered(ordered)?;
6061            }
6062        }
6063        if let Some(limit) = &intersect.limit {
6064            if self.config.pretty {
6065                self.write_newline();
6066            } else {
6067                self.write_space();
6068            }
6069            self.write_keyword("LIMIT");
6070            self.write_space();
6071            self.generate_expression(limit)?;
6072        }
6073        if let Some(offset) = &intersect.offset {
6074            if self.config.pretty {
6075                self.write_newline();
6076            } else {
6077                self.write_space();
6078            }
6079            self.write_keyword("OFFSET");
6080            self.write_space();
6081            self.generate_expression(offset)?;
6082        }
6083        // DISTRIBUTE BY (Hive/Spark)
6084        if let Some(distribute_by) = &intersect.distribute_by {
6085            self.write_space();
6086            self.write_keyword("DISTRIBUTE BY");
6087            self.write_space();
6088            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6089                if i > 0 {
6090                    self.write(", ");
6091                }
6092                self.generate_expression(expr)?;
6093            }
6094        }
6095        // SORT BY (Hive/Spark)
6096        if let Some(sort_by) = &intersect.sort_by {
6097            self.write_space();
6098            self.write_keyword("SORT BY");
6099            self.write_space();
6100            for (i, ord) in sort_by.expressions.iter().enumerate() {
6101                if i > 0 {
6102                    self.write(", ");
6103                }
6104                self.generate_ordered(ord)?;
6105            }
6106        }
6107        // CLUSTER BY (Hive/Spark)
6108        if let Some(cluster_by) = &intersect.cluster_by {
6109            self.write_space();
6110            self.write_keyword("CLUSTER BY");
6111            self.write_space();
6112            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6113                if i > 0 {
6114                    self.write(", ");
6115                }
6116                self.generate_ordered(ord)?;
6117            }
6118        }
6119        Ok(())
6120    }
6121
6122    fn generate_except(&mut self, except: &Except) -> Result<()> {
6123        use crate::dialects::DialectType;
6124
6125        // WITH clause
6126        if let Some(with) = &except.with {
6127            self.generate_with(with)?;
6128            self.write_space();
6129        }
6130
6131        self.generate_expression(&except.left)?;
6132        if self.config.pretty {
6133            self.write_newline();
6134            self.write_indent();
6135        } else {
6136            self.write_space();
6137        }
6138
6139        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6140        if let Some(side) = &except.side {
6141            self.write_keyword(side);
6142            self.write_space();
6143        }
6144        if let Some(kind) = &except.kind {
6145            self.write_keyword(kind);
6146            self.write_space();
6147        }
6148
6149        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6150        match self.config.dialect {
6151            Some(DialectType::Oracle) if !except.all => {
6152                self.write_keyword("MINUS");
6153            }
6154            Some(DialectType::ClickHouse) => {
6155                // ClickHouse: drop ALL from EXCEPT ALL
6156                self.write_keyword("EXCEPT");
6157                if except.distinct {
6158                    self.write_space();
6159                    self.write_keyword("DISTINCT");
6160                }
6161            }
6162            Some(DialectType::BigQuery) => {
6163                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6164                self.write_keyword("EXCEPT");
6165                if except.all {
6166                    self.write_space();
6167                    self.write_keyword("ALL");
6168                } else {
6169                    self.write_space();
6170                    self.write_keyword("DISTINCT");
6171                }
6172            }
6173            _ => {
6174                self.write_keyword("EXCEPT");
6175                if except.all {
6176                    self.write_space();
6177                    self.write_keyword("ALL");
6178                } else if except.distinct {
6179                    self.write_space();
6180                    self.write_keyword("DISTINCT");
6181                }
6182            }
6183        }
6184
6185        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6186        // DuckDB: BY NAME
6187        if except.corresponding || except.by_name {
6188            self.write_space();
6189            self.write_keyword("BY NAME");
6190        }
6191        if !except.on_columns.is_empty() {
6192            self.write_space();
6193            self.write_keyword("ON");
6194            self.write(" (");
6195            for (i, col) in except.on_columns.iter().enumerate() {
6196                if i > 0 {
6197                    self.write(", ");
6198                }
6199                self.generate_expression(col)?;
6200            }
6201            self.write(")");
6202        }
6203
6204        if self.config.pretty {
6205            self.write_newline();
6206            self.write_indent();
6207        } else {
6208            self.write_space();
6209        }
6210        self.generate_expression(&except.right)?;
6211        // ORDER BY, LIMIT, OFFSET for the set operation
6212        if let Some(order_by) = &except.order_by {
6213            if self.config.pretty {
6214                self.write_newline();
6215            } else {
6216                self.write_space();
6217            }
6218            self.write_keyword("ORDER BY");
6219            self.write_space();
6220            for (i, ordered) in order_by.expressions.iter().enumerate() {
6221                if i > 0 {
6222                    self.write(", ");
6223                }
6224                self.generate_ordered(ordered)?;
6225            }
6226        }
6227        if let Some(limit) = &except.limit {
6228            if self.config.pretty {
6229                self.write_newline();
6230            } else {
6231                self.write_space();
6232            }
6233            self.write_keyword("LIMIT");
6234            self.write_space();
6235            self.generate_expression(limit)?;
6236        }
6237        if let Some(offset) = &except.offset {
6238            if self.config.pretty {
6239                self.write_newline();
6240            } else {
6241                self.write_space();
6242            }
6243            self.write_keyword("OFFSET");
6244            self.write_space();
6245            self.generate_expression(offset)?;
6246        }
6247        // DISTRIBUTE BY (Hive/Spark)
6248        if let Some(distribute_by) = &except.distribute_by {
6249            self.write_space();
6250            self.write_keyword("DISTRIBUTE BY");
6251            self.write_space();
6252            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6253                if i > 0 {
6254                    self.write(", ");
6255                }
6256                self.generate_expression(expr)?;
6257            }
6258        }
6259        // SORT BY (Hive/Spark)
6260        if let Some(sort_by) = &except.sort_by {
6261            self.write_space();
6262            self.write_keyword("SORT BY");
6263            self.write_space();
6264            for (i, ord) in sort_by.expressions.iter().enumerate() {
6265                if i > 0 {
6266                    self.write(", ");
6267                }
6268                self.generate_ordered(ord)?;
6269            }
6270        }
6271        // CLUSTER BY (Hive/Spark)
6272        if let Some(cluster_by) = &except.cluster_by {
6273            self.write_space();
6274            self.write_keyword("CLUSTER BY");
6275            self.write_space();
6276            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6277                if i > 0 {
6278                    self.write(", ");
6279                }
6280                self.generate_ordered(ord)?;
6281            }
6282        }
6283        Ok(())
6284    }
6285
6286    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6287        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6288        let prepend_query_cte = if insert.with.is_none() {
6289            use crate::dialects::DialectType;
6290            let should_prepend = matches!(
6291                self.config.dialect,
6292                Some(DialectType::TSQL)
6293                    | Some(DialectType::Fabric)
6294                    | Some(DialectType::Spark)
6295                    | Some(DialectType::Databricks)
6296                    | Some(DialectType::Hive)
6297            );
6298            if should_prepend {
6299                if let Some(Expression::Select(select)) = &insert.query {
6300                    select.with.clone()
6301                } else {
6302                    None
6303                }
6304            } else {
6305                None
6306            }
6307        } else {
6308            None
6309        };
6310
6311        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6312        if let Some(with) = &insert.with {
6313            self.generate_with(with)?;
6314            self.write_space();
6315        } else if let Some(with) = &prepend_query_cte {
6316            self.generate_with(with)?;
6317            self.write_space();
6318        }
6319
6320        // Output leading comments before INSERT
6321        for comment in &insert.leading_comments {
6322            self.write_formatted_comment(comment);
6323            self.write(" ");
6324        }
6325
6326        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6327        if let Some(dir) = &insert.directory {
6328            self.write_keyword("INSERT OVERWRITE");
6329            if dir.local {
6330                self.write_space();
6331                self.write_keyword("LOCAL");
6332            }
6333            self.write_space();
6334            self.write_keyword("DIRECTORY");
6335            self.write_space();
6336            self.write("'");
6337            self.write(&dir.path);
6338            self.write("'");
6339
6340            // ROW FORMAT clause
6341            if let Some(row_format) = &dir.row_format {
6342                self.write_space();
6343                self.write_keyword("ROW FORMAT");
6344                if row_format.delimited {
6345                    self.write_space();
6346                    self.write_keyword("DELIMITED");
6347                }
6348                if let Some(val) = &row_format.fields_terminated_by {
6349                    self.write_space();
6350                    self.write_keyword("FIELDS TERMINATED BY");
6351                    self.write_space();
6352                    self.write("'");
6353                    self.write(val);
6354                    self.write("'");
6355                }
6356                if let Some(val) = &row_format.collection_items_terminated_by {
6357                    self.write_space();
6358                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6359                    self.write_space();
6360                    self.write("'");
6361                    self.write(val);
6362                    self.write("'");
6363                }
6364                if let Some(val) = &row_format.map_keys_terminated_by {
6365                    self.write_space();
6366                    self.write_keyword("MAP KEYS TERMINATED BY");
6367                    self.write_space();
6368                    self.write("'");
6369                    self.write(val);
6370                    self.write("'");
6371                }
6372                if let Some(val) = &row_format.lines_terminated_by {
6373                    self.write_space();
6374                    self.write_keyword("LINES TERMINATED BY");
6375                    self.write_space();
6376                    self.write("'");
6377                    self.write(val);
6378                    self.write("'");
6379                }
6380                if let Some(val) = &row_format.null_defined_as {
6381                    self.write_space();
6382                    self.write_keyword("NULL DEFINED AS");
6383                    self.write_space();
6384                    self.write("'");
6385                    self.write(val);
6386                    self.write("'");
6387                }
6388            }
6389
6390            // STORED AS clause
6391            if let Some(format) = &dir.stored_as {
6392                self.write_space();
6393                self.write_keyword("STORED AS");
6394                self.write_space();
6395                self.write_keyword(format);
6396            }
6397
6398            // Query (SELECT statement)
6399            if let Some(query) = &insert.query {
6400                self.write_space();
6401                self.generate_expression(query)?;
6402            }
6403
6404            return Ok(());
6405        }
6406
6407        if insert.is_replace {
6408            // MySQL/SQLite REPLACE INTO statement
6409            self.write_keyword("REPLACE INTO");
6410        } else if insert.overwrite {
6411            // Use dialect-specific INSERT OVERWRITE format
6412            self.write_keyword("INSERT");
6413            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6414            if let Some(ref hint) = insert.hint {
6415                self.generate_hint(hint)?;
6416            }
6417            self.write(&self.config.insert_overwrite.to_uppercase());
6418        } else if let Some(ref action) = insert.conflict_action {
6419            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6420            self.write_keyword("INSERT OR");
6421            self.write_space();
6422            self.write_keyword(action);
6423            self.write_space();
6424            self.write_keyword("INTO");
6425        } else if insert.ignore {
6426            // MySQL INSERT IGNORE syntax
6427            self.write_keyword("INSERT IGNORE INTO");
6428        } else {
6429            self.write_keyword("INSERT");
6430            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6431            if let Some(ref hint) = insert.hint {
6432                self.generate_hint(hint)?;
6433            }
6434            self.write_space();
6435            self.write_keyword("INTO");
6436        }
6437        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6438        if let Some(ref func) = insert.function_target {
6439            self.write_space();
6440            self.write_keyword("FUNCTION");
6441            self.write_space();
6442            self.generate_expression(func)?;
6443        } else {
6444            self.write_space();
6445            self.generate_table(&insert.table)?;
6446        }
6447
6448        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6449        if let Some(ref alias) = insert.alias {
6450            self.write_space();
6451            if insert.alias_explicit_as {
6452                self.write_keyword("AS");
6453                self.write_space();
6454            }
6455            self.generate_identifier(alias)?;
6456        }
6457
6458        // IF EXISTS clause (Hive)
6459        if insert.if_exists {
6460            self.write_space();
6461            self.write_keyword("IF EXISTS");
6462        }
6463
6464        // REPLACE WHERE clause (Databricks)
6465        if let Some(ref replace_where) = insert.replace_where {
6466            if self.config.pretty {
6467                self.write_newline();
6468                self.write_indent();
6469            } else {
6470                self.write_space();
6471            }
6472            self.write_keyword("REPLACE WHERE");
6473            self.write_space();
6474            self.generate_expression(replace_where)?;
6475        }
6476
6477        // Generate PARTITION clause if present
6478        if !insert.partition.is_empty() {
6479            self.write_space();
6480            self.write_keyword("PARTITION");
6481            self.write("(");
6482            for (i, (col, val)) in insert.partition.iter().enumerate() {
6483                if i > 0 {
6484                    self.write(", ");
6485                }
6486                self.generate_identifier(col)?;
6487                if let Some(v) = val {
6488                    self.write(" = ");
6489                    self.generate_expression(v)?;
6490                }
6491            }
6492            self.write(")");
6493        }
6494
6495        // ClickHouse: PARTITION BY expr
6496        if let Some(ref partition_by) = insert.partition_by {
6497            self.write_space();
6498            self.write_keyword("PARTITION BY");
6499            self.write_space();
6500            self.generate_expression(partition_by)?;
6501        }
6502
6503        // ClickHouse: SETTINGS key = val, ...
6504        if !insert.settings.is_empty() {
6505            self.write_space();
6506            self.write_keyword("SETTINGS");
6507            self.write_space();
6508            for (i, setting) in insert.settings.iter().enumerate() {
6509                if i > 0 {
6510                    self.write(", ");
6511                }
6512                self.generate_expression(setting)?;
6513            }
6514        }
6515
6516        if !insert.columns.is_empty() {
6517            if insert.alias.is_some() && insert.alias_explicit_as {
6518                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6519                self.write("(");
6520            } else {
6521                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6522                self.write(" (");
6523            }
6524            for (i, col) in insert.columns.iter().enumerate() {
6525                if i > 0 {
6526                    self.write(", ");
6527                }
6528                self.generate_identifier(col)?;
6529            }
6530            self.write(")");
6531        }
6532
6533        // OUTPUT clause (TSQL)
6534        if let Some(ref output) = insert.output {
6535            self.generate_output_clause(output)?;
6536        }
6537
6538        // BY NAME modifier (DuckDB)
6539        if insert.by_name {
6540            self.write_space();
6541            self.write_keyword("BY NAME");
6542        }
6543
6544        if insert.default_values {
6545            self.write_space();
6546            self.write_keyword("DEFAULT VALUES");
6547        } else if let Some(query) = &insert.query {
6548            if self.config.pretty {
6549                self.write_newline();
6550            } else {
6551                self.write_space();
6552            }
6553            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6554            if prepend_query_cte.is_some() {
6555                if let Expression::Select(select) = query {
6556                    let mut select_no_with = select.clone();
6557                    select_no_with.with = None;
6558                    self.generate_select(&select_no_with)?;
6559                } else {
6560                    self.generate_expression(query)?;
6561                }
6562            } else {
6563                self.generate_expression(query)?;
6564            }
6565        } else if !insert.values.is_empty() {
6566            if self.config.pretty {
6567                // Pretty printing: VALUES on new line, each tuple indented
6568                self.write_newline();
6569                self.write_keyword("VALUES");
6570                self.write_newline();
6571                self.indent_level += 1;
6572                for (i, row) in insert.values.iter().enumerate() {
6573                    if i > 0 {
6574                        self.write(",");
6575                        self.write_newline();
6576                    }
6577                    self.write_indent();
6578                    self.write("(");
6579                    for (j, val) in row.iter().enumerate() {
6580                        if j > 0 {
6581                            self.write(", ");
6582                        }
6583                        self.generate_expression(val)?;
6584                    }
6585                    self.write(")");
6586                }
6587                self.indent_level -= 1;
6588            } else {
6589                // Non-pretty: single line
6590                self.write_space();
6591                self.write_keyword("VALUES");
6592                for (i, row) in insert.values.iter().enumerate() {
6593                    if i > 0 {
6594                        self.write(",");
6595                    }
6596                    self.write(" (");
6597                    for (j, val) in row.iter().enumerate() {
6598                        if j > 0 {
6599                            self.write(", ");
6600                        }
6601                        self.generate_expression(val)?;
6602                    }
6603                    self.write(")");
6604                }
6605            }
6606        }
6607
6608        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6609        if let Some(ref source) = insert.source {
6610            self.write_space();
6611            self.write_keyword("TABLE");
6612            self.write_space();
6613            self.generate_expression(source)?;
6614        }
6615
6616        // Source alias (MySQL: VALUES (...) AS new_data)
6617        if let Some(alias) = &insert.source_alias {
6618            self.write_space();
6619            self.write_keyword("AS");
6620            self.write_space();
6621            self.generate_identifier(alias)?;
6622        }
6623
6624        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6625        if let Some(on_conflict) = &insert.on_conflict {
6626            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6627                self.write_space();
6628                self.generate_expression(on_conflict)?;
6629            }
6630        }
6631
6632        // RETURNING clause
6633        if !insert.returning.is_empty() {
6634            self.write_space();
6635            self.write_keyword("RETURNING");
6636            self.write_space();
6637            for (i, expr) in insert.returning.iter().enumerate() {
6638                if i > 0 {
6639                    self.write(", ");
6640                }
6641                self.generate_expression(expr)?;
6642            }
6643        }
6644
6645        Ok(())
6646    }
6647
6648    fn generate_update(&mut self, update: &Update) -> Result<()> {
6649        // Output leading comments before UPDATE
6650        for comment in &update.leading_comments {
6651            self.write_formatted_comment(comment);
6652            self.write(" ");
6653        }
6654
6655        // WITH clause (CTEs)
6656        if let Some(ref with) = update.with {
6657            self.generate_with(with)?;
6658            self.write_space();
6659        }
6660
6661        self.write_keyword("UPDATE");
6662        self.write_space();
6663        self.generate_table(&update.table)?;
6664
6665        let mysql_like_update_from = matches!(
6666            self.config.dialect,
6667            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6668        ) && update.from_clause.is_some();
6669
6670        let mut set_pairs = update.set.clone();
6671
6672        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6673        let mut pre_set_joins = update.table_joins.clone();
6674        if mysql_like_update_from {
6675            let target_name = update
6676                .table
6677                .alias
6678                .as_ref()
6679                .map(|a| a.name.clone())
6680                .unwrap_or_else(|| update.table.name.name.clone());
6681
6682            for (col, _) in &mut set_pairs {
6683                if !col.name.contains('.') {
6684                    col.name = format!("{}.{}", target_name, col.name);
6685                }
6686            }
6687
6688            if let Some(from_clause) = &update.from_clause {
6689                for table_expr in &from_clause.expressions {
6690                    pre_set_joins.push(crate::expressions::Join {
6691                        this: table_expr.clone(),
6692                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6693                            value: true,
6694                        })),
6695                        using: Vec::new(),
6696                        kind: crate::expressions::JoinKind::Inner,
6697                        use_inner_keyword: false,
6698                        use_outer_keyword: false,
6699                        deferred_condition: false,
6700                        join_hint: None,
6701                        match_condition: None,
6702                        pivots: Vec::new(),
6703                        comments: Vec::new(),
6704                        nesting_group: 0,
6705                        directed: false,
6706                    });
6707                }
6708            }
6709            for join in &update.from_joins {
6710                let mut join = join.clone();
6711                if join.on.is_none() && join.using.is_empty() {
6712                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6713                        value: true,
6714                    }));
6715                }
6716                pre_set_joins.push(join);
6717            }
6718        }
6719
6720        // Extra tables for multi-table UPDATE (MySQL syntax)
6721        for extra_table in &update.extra_tables {
6722            self.write(", ");
6723            self.generate_table(extra_table)?;
6724        }
6725
6726        // JOINs attached to the table list (MySQL multi-table syntax)
6727        for join in &pre_set_joins {
6728            // generate_join already adds a leading space
6729            self.generate_join(join)?;
6730        }
6731
6732        // Teradata: FROM clause comes before SET
6733        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6734        if teradata_from_before_set && !mysql_like_update_from {
6735            if let Some(ref from_clause) = update.from_clause {
6736                self.write_space();
6737                self.write_keyword("FROM");
6738                self.write_space();
6739                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6740                    if i > 0 {
6741                        self.write(", ");
6742                    }
6743                    self.generate_expression(table_expr)?;
6744                }
6745            }
6746            for join in &update.from_joins {
6747                self.generate_join(join)?;
6748            }
6749        }
6750
6751        self.write_space();
6752        self.write_keyword("SET");
6753        self.write_space();
6754
6755        for (i, (col, val)) in set_pairs.iter().enumerate() {
6756            if i > 0 {
6757                self.write(", ");
6758            }
6759            self.generate_identifier(col)?;
6760            self.write(" = ");
6761            self.generate_expression(val)?;
6762        }
6763
6764        // OUTPUT clause (TSQL)
6765        if let Some(ref output) = update.output {
6766            self.generate_output_clause(output)?;
6767        }
6768
6769        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6770        if !mysql_like_update_from && !teradata_from_before_set {
6771            if let Some(ref from_clause) = update.from_clause {
6772                self.write_space();
6773                self.write_keyword("FROM");
6774                self.write_space();
6775                // Generate each table in the FROM clause
6776                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6777                    if i > 0 {
6778                        self.write(", ");
6779                    }
6780                    self.generate_expression(table_expr)?;
6781                }
6782            }
6783        }
6784
6785        if !mysql_like_update_from && !teradata_from_before_set {
6786            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6787            for join in &update.from_joins {
6788                self.generate_join(join)?;
6789            }
6790        }
6791
6792        if let Some(where_clause) = &update.where_clause {
6793            self.write_space();
6794            self.write_keyword("WHERE");
6795            self.write_space();
6796            self.generate_expression(&where_clause.this)?;
6797        }
6798
6799        // RETURNING clause
6800        if !update.returning.is_empty() {
6801            self.write_space();
6802            self.write_keyword("RETURNING");
6803            self.write_space();
6804            for (i, expr) in update.returning.iter().enumerate() {
6805                if i > 0 {
6806                    self.write(", ");
6807                }
6808                self.generate_expression(expr)?;
6809            }
6810        }
6811
6812        // ORDER BY clause (MySQL)
6813        if let Some(ref order_by) = update.order_by {
6814            self.write_space();
6815            self.generate_order_by(order_by)?;
6816        }
6817
6818        // LIMIT clause (MySQL)
6819        if let Some(ref limit) = update.limit {
6820            self.write_space();
6821            self.write_keyword("LIMIT");
6822            self.write_space();
6823            self.generate_expression(limit)?;
6824        }
6825
6826        Ok(())
6827    }
6828
6829    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6830        // Output WITH clause if present
6831        if let Some(with) = &delete.with {
6832            self.generate_with(with)?;
6833            self.write_space();
6834        }
6835
6836        // Output leading comments before DELETE
6837        for comment in &delete.leading_comments {
6838            self.write_formatted_comment(comment);
6839            self.write(" ");
6840        }
6841
6842        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6843        if !delete.tables.is_empty() && !delete.tables_from_using {
6844            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6845            self.write_keyword("DELETE");
6846            self.write_space();
6847            for (i, tbl) in delete.tables.iter().enumerate() {
6848                if i > 0 {
6849                    self.write(", ");
6850                }
6851                self.generate_table(tbl)?;
6852            }
6853            // TSQL: OUTPUT clause between target table and FROM
6854            if let Some(ref output) = delete.output {
6855                self.generate_output_clause(output)?;
6856            }
6857            self.write_space();
6858            self.write_keyword("FROM");
6859            self.write_space();
6860            self.generate_table(&delete.table)?;
6861        } else if !delete.tables.is_empty() && delete.tables_from_using {
6862            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6863            self.write_keyword("DELETE FROM");
6864            self.write_space();
6865            for (i, tbl) in delete.tables.iter().enumerate() {
6866                if i > 0 {
6867                    self.write(", ");
6868                }
6869                self.generate_table(tbl)?;
6870            }
6871        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6872            // BigQuery-style DELETE without FROM keyword
6873            self.write_keyword("DELETE");
6874            self.write_space();
6875            self.generate_table(&delete.table)?;
6876        } else {
6877            self.write_keyword("DELETE FROM");
6878            self.write_space();
6879            self.generate_table(&delete.table)?;
6880        }
6881
6882        // ClickHouse: ON CLUSTER clause
6883        if let Some(ref on_cluster) = delete.on_cluster {
6884            self.write_space();
6885            self.generate_on_cluster(on_cluster)?;
6886        }
6887
6888        // FORCE INDEX hint (MySQL)
6889        if let Some(ref idx) = delete.force_index {
6890            self.write_space();
6891            self.write_keyword("FORCE INDEX");
6892            self.write(" (");
6893            self.write(idx);
6894            self.write(")");
6895        }
6896
6897        // Optional alias
6898        if let Some(ref alias) = delete.alias {
6899            self.write_space();
6900            if delete.alias_explicit_as
6901                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6902            {
6903                self.write_keyword("AS");
6904                self.write_space();
6905            }
6906            self.generate_identifier(alias)?;
6907        }
6908
6909        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6910        if !delete.tables_from_using {
6911            for join in &delete.joins {
6912                self.generate_join(join)?;
6913            }
6914        }
6915
6916        // USING clause (PostgreSQL/DuckDB/MySQL)
6917        if !delete.using.is_empty() {
6918            self.write_space();
6919            self.write_keyword("USING");
6920            for (i, table) in delete.using.iter().enumerate() {
6921                if i > 0 {
6922                    self.write(",");
6923                }
6924                self.write_space();
6925                // Check if the table has subquery hints (DuckDB USING with subquery)
6926                if !table.hints.is_empty() && table.name.is_empty() {
6927                    // Subquery in USING: (VALUES ...) AS alias(cols)
6928                    self.generate_expression(&table.hints[0])?;
6929                    if let Some(ref alias) = table.alias {
6930                        self.write_space();
6931                        if table.alias_explicit_as {
6932                            self.write_keyword("AS");
6933                            self.write_space();
6934                        }
6935                        self.generate_identifier(alias)?;
6936                        if !table.column_aliases.is_empty() {
6937                            self.write("(");
6938                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6939                                if j > 0 {
6940                                    self.write(", ");
6941                                }
6942                                self.generate_identifier(col_alias)?;
6943                            }
6944                            self.write(")");
6945                        }
6946                    }
6947                } else {
6948                    self.generate_table(table)?;
6949                }
6950            }
6951        }
6952
6953        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6954        if delete.tables_from_using {
6955            for join in &delete.joins {
6956                self.generate_join(join)?;
6957            }
6958        }
6959
6960        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6961        let output_already_emitted =
6962            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6963        if !output_already_emitted {
6964            if let Some(ref output) = delete.output {
6965                self.generate_output_clause(output)?;
6966            }
6967        }
6968
6969        if let Some(where_clause) = &delete.where_clause {
6970            self.write_space();
6971            self.write_keyword("WHERE");
6972            self.write_space();
6973            self.generate_expression(&where_clause.this)?;
6974        }
6975
6976        // ORDER BY clause (MySQL)
6977        if let Some(ref order_by) = delete.order_by {
6978            self.write_space();
6979            self.generate_order_by(order_by)?;
6980        }
6981
6982        // LIMIT clause (MySQL)
6983        if let Some(ref limit) = delete.limit {
6984            self.write_space();
6985            self.write_keyword("LIMIT");
6986            self.write_space();
6987            self.generate_expression(limit)?;
6988        }
6989
6990        // RETURNING clause (PostgreSQL)
6991        if !delete.returning.is_empty() {
6992            self.write_space();
6993            self.write_keyword("RETURNING");
6994            self.write_space();
6995            for (i, expr) in delete.returning.iter().enumerate() {
6996                if i > 0 {
6997                    self.write(", ");
6998                }
6999                self.generate_expression(expr)?;
7000            }
7001        }
7002
7003        Ok(())
7004    }
7005
7006    // ==================== DDL Generation ====================
7007
7008    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7009        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7010        // CREATE TABLE AS SELECT uses Trino (double quotes)
7011        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7012        let saved_athena_hive_context = self.athena_hive_context;
7013        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7014        if matches!(
7015            self.config.dialect,
7016            Some(crate::dialects::DialectType::Athena)
7017        ) {
7018            // Use Hive context if:
7019            // 1. It's an EXTERNAL table, OR
7020            // 2. There's no AS SELECT clause
7021            let is_external = ct
7022                .table_modifier
7023                .as_ref()
7024                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7025                .unwrap_or(false);
7026            let has_as_select = ct.as_select.is_some();
7027            self.athena_hive_context = is_external || !has_as_select;
7028        }
7029
7030        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7031        if matches!(
7032            self.config.dialect,
7033            Some(crate::dialects::DialectType::TSQL)
7034        ) {
7035            if let Some(ref query) = ct.as_select {
7036                // Output WITH CTE clause if present
7037                if let Some(with_cte) = &ct.with_cte {
7038                    self.generate_with(with_cte)?;
7039                    self.write_space();
7040                }
7041
7042                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7043                self.write_keyword("SELECT");
7044                self.write(" * ");
7045                self.write_keyword("INTO");
7046                self.write_space();
7047
7048                // If temporary, prefix with # for TSQL temp table
7049                if ct.temporary {
7050                    self.write("#");
7051                }
7052                self.generate_table(&ct.name)?;
7053
7054                self.write_space();
7055                self.write_keyword("FROM");
7056                self.write(" (");
7057                // For TSQL, add aliases to select columns to preserve column names
7058                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7059                self.generate_expression(&aliased_query)?;
7060                self.write(") ");
7061                self.write_keyword("AS");
7062                self.write(" temp");
7063                return Ok(());
7064            }
7065        }
7066
7067        // Output WITH CTE clause if present
7068        if let Some(with_cte) = &ct.with_cte {
7069            self.generate_with(with_cte)?;
7070            self.write_space();
7071        }
7072
7073        // Output leading comments before CREATE
7074        for comment in &ct.leading_comments {
7075            self.write_formatted_comment(comment);
7076            self.write(" ");
7077        }
7078        self.write_keyword("CREATE");
7079
7080        if ct.or_replace {
7081            self.write_space();
7082            self.write_keyword("OR REPLACE");
7083        }
7084
7085        if ct.temporary {
7086            self.write_space();
7087            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7088            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7089                self.write_keyword("GLOBAL TEMPORARY");
7090            } else {
7091                self.write_keyword("TEMPORARY");
7092            }
7093        }
7094
7095        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7096        let is_dictionary = ct
7097            .table_modifier
7098            .as_ref()
7099            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7100            .unwrap_or(false);
7101        if let Some(ref modifier) = ct.table_modifier {
7102            // TRANSIENT is Snowflake-specific - skip for other dialects
7103            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7104                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7105            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7106            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7107                || modifier.eq_ignore_ascii_case("SET")
7108                || modifier.eq_ignore_ascii_case("MULTISET")
7109                || modifier.to_uppercase().contains("VOLATILE")
7110                || modifier.to_uppercase().starts_with("SET ")
7111                || modifier.to_uppercase().starts_with("MULTISET ");
7112            let skip_teradata =
7113                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7114            if !skip_transient && !skip_teradata {
7115                self.write_space();
7116                self.write_keyword(modifier);
7117            }
7118        }
7119
7120        if !is_dictionary {
7121            self.write_space();
7122            self.write_keyword("TABLE");
7123        }
7124
7125        if ct.if_not_exists {
7126            self.write_space();
7127            self.write_keyword("IF NOT EXISTS");
7128        }
7129
7130        self.write_space();
7131        self.generate_table(&ct.name)?;
7132
7133        // ClickHouse: ON CLUSTER clause
7134        if let Some(ref on_cluster) = ct.on_cluster {
7135            self.write_space();
7136            self.generate_on_cluster(on_cluster)?;
7137        }
7138
7139        // Teradata: options after table name before column list (comma-separated)
7140        if matches!(
7141            self.config.dialect,
7142            Some(crate::dialects::DialectType::Teradata)
7143        ) && !ct.teradata_post_name_options.is_empty()
7144        {
7145            for opt in &ct.teradata_post_name_options {
7146                self.write(", ");
7147                self.write(opt);
7148            }
7149        }
7150
7151        // Snowflake: COPY GRANTS clause
7152        if ct.copy_grants {
7153            self.write_space();
7154            self.write_keyword("COPY GRANTS");
7155        }
7156
7157        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7158        if let Some(ref using_template) = ct.using_template {
7159            self.write_space();
7160            self.write_keyword("USING TEMPLATE");
7161            self.write_space();
7162            self.generate_expression(using_template)?;
7163            return Ok(());
7164        }
7165
7166        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7167        if let Some(ref clone_source) = ct.clone_source {
7168            self.write_space();
7169            if ct.is_copy && self.config.supports_table_copy {
7170                // BigQuery uses COPY
7171                self.write_keyword("COPY");
7172            } else if ct.shallow_clone {
7173                self.write_keyword("SHALLOW CLONE");
7174            } else {
7175                self.write_keyword("CLONE");
7176            }
7177            self.write_space();
7178            self.generate_table(clone_source)?;
7179            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7180            if let Some(ref at_clause) = ct.clone_at_clause {
7181                self.write_space();
7182                self.generate_expression(at_clause)?;
7183            }
7184            return Ok(());
7185        }
7186
7187        // Handle PARTITION OF property
7188        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7189        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7190        if let Some(ref partition_of) = ct.partition_of {
7191            self.write_space();
7192
7193            // Extract the PartitionedOfProperty parts to generate them separately
7194            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7195                // Output: PARTITION OF <table>
7196                self.write_keyword("PARTITION OF");
7197                self.write_space();
7198                self.generate_expression(&pop.this)?;
7199
7200                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7201                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7202                    self.write(" (");
7203                    let mut first = true;
7204                    for col in &ct.columns {
7205                        if !first {
7206                            self.write(", ");
7207                        }
7208                        first = false;
7209                        self.generate_column_def(col)?;
7210                    }
7211                    for constraint in &ct.constraints {
7212                        if !first {
7213                            self.write(", ");
7214                        }
7215                        first = false;
7216                        self.generate_table_constraint(constraint)?;
7217                    }
7218                    self.write(")");
7219                }
7220
7221                // Output partition bound spec: FOR VALUES ... or DEFAULT
7222                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7223                    self.write_space();
7224                    self.write_keyword("FOR VALUES");
7225                    self.write_space();
7226                    self.generate_expression(&pop.expression)?;
7227                } else {
7228                    self.write_space();
7229                    self.write_keyword("DEFAULT");
7230                }
7231            } else {
7232                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7233                self.generate_expression(partition_of)?;
7234
7235                // Output columns/constraints if present
7236                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7237                    self.write(" (");
7238                    let mut first = true;
7239                    for col in &ct.columns {
7240                        if !first {
7241                            self.write(", ");
7242                        }
7243                        first = false;
7244                        self.generate_column_def(col)?;
7245                    }
7246                    for constraint in &ct.constraints {
7247                        if !first {
7248                            self.write(", ");
7249                        }
7250                        first = false;
7251                        self.generate_table_constraint(constraint)?;
7252                    }
7253                    self.write(")");
7254                }
7255            }
7256
7257            // Output table properties (e.g., PARTITION BY RANGE(population))
7258            for prop in &ct.properties {
7259                self.write_space();
7260                self.generate_expression(prop)?;
7261            }
7262
7263            return Ok(());
7264        }
7265
7266        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7267        // This matches Python sqlglot's behavior for SQLite dialect
7268        self.sqlite_inline_pk_columns.clear();
7269        if matches!(
7270            self.config.dialect,
7271            Some(crate::dialects::DialectType::SQLite)
7272        ) {
7273            for constraint in &ct.constraints {
7274                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7275                    // Only inline if: single column, no constraint name, and column exists in table
7276                    if columns.len() == 1 && name.is_none() {
7277                        let pk_col_name = columns[0].name.to_lowercase();
7278                        // Check if this column exists in the table
7279                        if ct
7280                            .columns
7281                            .iter()
7282                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7283                        {
7284                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7285                        }
7286                    }
7287                }
7288            }
7289        }
7290
7291        // Output columns if present (even for CTAS with columns)
7292        if !ct.columns.is_empty() {
7293            if self.config.pretty {
7294                // Pretty print: each column on new line
7295                self.write(" (");
7296                self.write_newline();
7297                self.indent_level += 1;
7298                for (i, col) in ct.columns.iter().enumerate() {
7299                    if i > 0 {
7300                        self.write(",");
7301                        self.write_newline();
7302                    }
7303                    self.write_indent();
7304                    self.generate_column_def(col)?;
7305                }
7306                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7307                for constraint in &ct.constraints {
7308                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7309                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7310                        if columns.len() == 1
7311                            && name.is_none()
7312                            && self
7313                                .sqlite_inline_pk_columns
7314                                .contains(&columns[0].name.to_lowercase())
7315                        {
7316                            continue;
7317                        }
7318                    }
7319                    self.write(",");
7320                    self.write_newline();
7321                    self.write_indent();
7322                    self.generate_table_constraint(constraint)?;
7323                }
7324                self.indent_level -= 1;
7325                self.write_newline();
7326                self.write(")");
7327            } else {
7328                self.write(" (");
7329                for (i, col) in ct.columns.iter().enumerate() {
7330                    if i > 0 {
7331                        self.write(", ");
7332                    }
7333                    self.generate_column_def(col)?;
7334                }
7335                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7336                let mut first_constraint = true;
7337                for constraint in &ct.constraints {
7338                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7339                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7340                        if columns.len() == 1
7341                            && name.is_none()
7342                            && self
7343                                .sqlite_inline_pk_columns
7344                                .contains(&columns[0].name.to_lowercase())
7345                        {
7346                            continue;
7347                        }
7348                    }
7349                    if first_constraint {
7350                        self.write(", ");
7351                        first_constraint = false;
7352                    } else {
7353                        self.write(", ");
7354                    }
7355                    self.generate_table_constraint(constraint)?;
7356                }
7357                self.write(")");
7358            }
7359        } else if !ct.constraints.is_empty() {
7360            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7361            let has_like_only = ct
7362                .constraints
7363                .iter()
7364                .all(|c| matches!(c, TableConstraint::Like { .. }));
7365            let has_tags_only = ct
7366                .constraints
7367                .iter()
7368                .all(|c| matches!(c, TableConstraint::Tags(_)));
7369            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7370            // Most dialects: CREATE TABLE A LIKE B (no parens)
7371            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7372            let is_pg_like = matches!(
7373                self.config.dialect,
7374                Some(crate::dialects::DialectType::PostgreSQL)
7375                    | Some(crate::dialects::DialectType::CockroachDB)
7376                    | Some(crate::dialects::DialectType::Materialize)
7377                    | Some(crate::dialects::DialectType::RisingWave)
7378                    | Some(crate::dialects::DialectType::Redshift)
7379                    | Some(crate::dialects::DialectType::Presto)
7380                    | Some(crate::dialects::DialectType::Trino)
7381                    | Some(crate::dialects::DialectType::Athena)
7382            );
7383            let use_parens = if has_like_only {
7384                is_pg_like
7385            } else {
7386                !has_tags_only
7387            };
7388            if self.config.pretty && use_parens {
7389                self.write(" (");
7390                self.write_newline();
7391                self.indent_level += 1;
7392                for (i, constraint) in ct.constraints.iter().enumerate() {
7393                    if i > 0 {
7394                        self.write(",");
7395                        self.write_newline();
7396                    }
7397                    self.write_indent();
7398                    self.generate_table_constraint(constraint)?;
7399                }
7400                self.indent_level -= 1;
7401                self.write_newline();
7402                self.write(")");
7403            } else {
7404                if use_parens {
7405                    self.write(" (");
7406                } else {
7407                    self.write_space();
7408                }
7409                for (i, constraint) in ct.constraints.iter().enumerate() {
7410                    if i > 0 {
7411                        self.write(", ");
7412                    }
7413                    self.generate_table_constraint(constraint)?;
7414                }
7415                if use_parens {
7416                    self.write(")");
7417                }
7418            }
7419        }
7420
7421        // TSQL ON filegroup or ON filegroup (partition_column) clause
7422        if let Some(ref on_prop) = ct.on_property {
7423            self.write(" ");
7424            self.write_keyword("ON");
7425            self.write(" ");
7426            self.generate_expression(&on_prop.this)?;
7427        }
7428
7429        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7430        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7431        if !is_clickhouse {
7432            for prop in &ct.properties {
7433                if let Expression::SchemaCommentProperty(_) = prop {
7434                    if self.config.pretty {
7435                        self.write_newline();
7436                    } else {
7437                        self.write_space();
7438                    }
7439                    self.generate_expression(prop)?;
7440                }
7441            }
7442        }
7443
7444        // WITH properties (output after columns if columns exist, otherwise before AS)
7445        if !ct.with_properties.is_empty() {
7446            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7447            let is_snowflake_special_table = matches!(
7448                self.config.dialect,
7449                Some(crate::dialects::DialectType::Snowflake)
7450            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7451                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7452            if is_snowflake_special_table {
7453                for (key, value) in &ct.with_properties {
7454                    self.write_space();
7455                    self.write(key);
7456                    self.write("=");
7457                    self.write(value);
7458                }
7459            } else if self.config.pretty {
7460                self.write_newline();
7461                self.write_keyword("WITH");
7462                self.write(" (");
7463                self.write_newline();
7464                self.indent_level += 1;
7465                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7466                    if i > 0 {
7467                        self.write(",");
7468                        self.write_newline();
7469                    }
7470                    self.write_indent();
7471                    self.write(key);
7472                    self.write("=");
7473                    self.write(value);
7474                }
7475                self.indent_level -= 1;
7476                self.write_newline();
7477                self.write(")");
7478            } else {
7479                self.write_space();
7480                self.write_keyword("WITH");
7481                self.write(" (");
7482                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7483                    if i > 0 {
7484                        self.write(", ");
7485                    }
7486                    self.write(key);
7487                    self.write("=");
7488                    self.write(value);
7489                }
7490                self.write(")");
7491            }
7492        }
7493
7494        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7495            if is_clickhouse && ct.as_select.is_some() {
7496                let mut pre = Vec::new();
7497                let mut post = Vec::new();
7498                for prop in &ct.properties {
7499                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7500                        post.push(prop);
7501                    } else {
7502                        pre.push(prop);
7503                    }
7504                }
7505                (pre, post)
7506            } else {
7507                (ct.properties.iter().collect(), Vec::new())
7508            };
7509
7510        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7511        for prop in pre_as_properties {
7512            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7513            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7514                continue;
7515            }
7516            if self.config.pretty {
7517                self.write_newline();
7518            } else {
7519                self.write_space();
7520            }
7521            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7522            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7523            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7524            if let Expression::Properties(props) = prop {
7525                let is_hive_dialect = matches!(
7526                    self.config.dialect,
7527                    Some(crate::dialects::DialectType::Hive)
7528                        | Some(crate::dialects::DialectType::Spark)
7529                        | Some(crate::dialects::DialectType::Databricks)
7530                        | Some(crate::dialects::DialectType::Athena)
7531                );
7532                let is_doris_starrocks = matches!(
7533                    self.config.dialect,
7534                    Some(crate::dialects::DialectType::Doris)
7535                        | Some(crate::dialects::DialectType::StarRocks)
7536                );
7537                if is_hive_dialect {
7538                    self.generate_tblproperties_clause(&props.expressions)?;
7539                } else if is_doris_starrocks {
7540                    self.generate_properties_clause(&props.expressions)?;
7541                } else {
7542                    self.generate_options_clause(&props.expressions)?;
7543                }
7544            } else {
7545                self.generate_expression(prop)?;
7546            }
7547        }
7548
7549        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7550        for prop in &ct.post_table_properties {
7551            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7552                self.write(" WITH(");
7553                self.generate_system_versioning_content(svp)?;
7554                self.write(")");
7555            } else if let Expression::Properties(props) = prop {
7556                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7557                let is_doris_starrocks = matches!(
7558                    self.config.dialect,
7559                    Some(crate::dialects::DialectType::Doris)
7560                        | Some(crate::dialects::DialectType::StarRocks)
7561                );
7562                self.write_space();
7563                if is_doris_starrocks {
7564                    self.generate_properties_clause(&props.expressions)?;
7565                } else {
7566                    self.generate_options_clause(&props.expressions)?;
7567                }
7568            } else {
7569                self.write_space();
7570                self.generate_expression(prop)?;
7571            }
7572        }
7573
7574        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7575        // Only output for StarRocks target
7576        if let Some(ref rollup) = ct.rollup {
7577            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7578                self.write_space();
7579                self.generate_rollup_property(rollup)?;
7580            }
7581        }
7582
7583        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7584        // Only output for MySQL-compatible dialects; strip for others during transpilation
7585        // COMMENT is also used by Hive/Spark so we selectively preserve it
7586        let is_mysql_compatible = matches!(
7587            self.config.dialect,
7588            Some(DialectType::MySQL)
7589                | Some(DialectType::SingleStore)
7590                | Some(DialectType::Doris)
7591                | Some(DialectType::StarRocks)
7592                | None
7593        );
7594        let is_hive_compatible = matches!(
7595            self.config.dialect,
7596            Some(DialectType::Hive)
7597                | Some(DialectType::Spark)
7598                | Some(DialectType::Databricks)
7599                | Some(DialectType::Athena)
7600        );
7601        let mysql_pretty_options =
7602            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7603        for (key, value) in &ct.mysql_table_options {
7604            // Skip non-MySQL-specific options for non-MySQL targets
7605            let should_output = if is_mysql_compatible {
7606                true
7607            } else if is_hive_compatible && key == "COMMENT" {
7608                true // COMMENT is valid in Hive/Spark table definitions
7609            } else {
7610                false
7611            };
7612            if should_output {
7613                if mysql_pretty_options {
7614                    self.write_newline();
7615                    self.write_indent();
7616                } else {
7617                    self.write_space();
7618                }
7619                self.write_keyword(key);
7620                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7621                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7622                    self.write_space();
7623                } else {
7624                    self.write("=");
7625                }
7626                self.write(value);
7627            }
7628        }
7629
7630        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7631        if ct.temporary
7632            && matches!(
7633                self.config.dialect,
7634                Some(DialectType::Spark) | Some(DialectType::Databricks)
7635            )
7636            && ct.as_select.is_none()
7637        {
7638            self.write_space();
7639            self.write_keyword("USING PARQUET");
7640        }
7641
7642        // PostgreSQL INHERITS clause
7643        if !ct.inherits.is_empty() {
7644            self.write_space();
7645            self.write_keyword("INHERITS");
7646            self.write(" (");
7647            for (i, parent) in ct.inherits.iter().enumerate() {
7648                if i > 0 {
7649                    self.write(", ");
7650                }
7651                self.generate_table(parent)?;
7652            }
7653            self.write(")");
7654        }
7655
7656        // CREATE TABLE AS SELECT
7657        if let Some(ref query) = ct.as_select {
7658            self.write_space();
7659            self.write_keyword("AS");
7660            self.write_space();
7661            if ct.as_select_parenthesized {
7662                self.write("(");
7663            }
7664            self.generate_expression(query)?;
7665            if ct.as_select_parenthesized {
7666                self.write(")");
7667            }
7668
7669            // Teradata: WITH DATA / WITH NO DATA
7670            if let Some(with_data) = ct.with_data {
7671                self.write_space();
7672                self.write_keyword("WITH");
7673                if !with_data {
7674                    self.write_space();
7675                    self.write_keyword("NO");
7676                }
7677                self.write_space();
7678                self.write_keyword("DATA");
7679            }
7680
7681            // Teradata: AND STATISTICS / AND NO STATISTICS
7682            if let Some(with_statistics) = ct.with_statistics {
7683                self.write_space();
7684                self.write_keyword("AND");
7685                if !with_statistics {
7686                    self.write_space();
7687                    self.write_keyword("NO");
7688                }
7689                self.write_space();
7690                self.write_keyword("STATISTICS");
7691            }
7692
7693            // Teradata: Index specifications
7694            for index in &ct.teradata_indexes {
7695                self.write_space();
7696                match index.kind {
7697                    TeradataIndexKind::NoPrimary => {
7698                        self.write_keyword("NO PRIMARY INDEX");
7699                    }
7700                    TeradataIndexKind::Primary => {
7701                        self.write_keyword("PRIMARY INDEX");
7702                    }
7703                    TeradataIndexKind::PrimaryAmp => {
7704                        self.write_keyword("PRIMARY AMP INDEX");
7705                    }
7706                    TeradataIndexKind::Unique => {
7707                        self.write_keyword("UNIQUE INDEX");
7708                    }
7709                    TeradataIndexKind::UniquePrimary => {
7710                        self.write_keyword("UNIQUE PRIMARY INDEX");
7711                    }
7712                    TeradataIndexKind::Secondary => {
7713                        self.write_keyword("INDEX");
7714                    }
7715                }
7716                // Output index name if present
7717                if let Some(ref name) = index.name {
7718                    self.write_space();
7719                    self.write(name);
7720                }
7721                // Output columns if present
7722                if !index.columns.is_empty() {
7723                    self.write(" (");
7724                    for (i, col) in index.columns.iter().enumerate() {
7725                        if i > 0 {
7726                            self.write(", ");
7727                        }
7728                        self.write(col);
7729                    }
7730                    self.write(")");
7731                }
7732            }
7733
7734            // Teradata: ON COMMIT behavior for volatile tables
7735            if let Some(ref on_commit) = ct.on_commit {
7736                self.write_space();
7737                self.write_keyword("ON COMMIT");
7738                self.write_space();
7739                match on_commit {
7740                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7741                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7742                }
7743            }
7744
7745            if !post_as_properties.is_empty() {
7746                for prop in post_as_properties {
7747                    self.write_space();
7748                    self.generate_expression(prop)?;
7749                }
7750            }
7751
7752            // Restore Athena Hive context before early return
7753            self.athena_hive_context = saved_athena_hive_context;
7754            return Ok(());
7755        }
7756
7757        // ON COMMIT behavior (for non-CTAS tables)
7758        if let Some(ref on_commit) = ct.on_commit {
7759            self.write_space();
7760            self.write_keyword("ON COMMIT");
7761            self.write_space();
7762            match on_commit {
7763                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7764                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7765            }
7766        }
7767
7768        // Restore Athena Hive context
7769        self.athena_hive_context = saved_athena_hive_context;
7770
7771        Ok(())
7772    }
7773
7774    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7775    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7776    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7777        // Output column name
7778        self.generate_identifier(&col.name)?;
7779        // Output data type if known
7780        if !matches!(col.data_type, DataType::Unknown) {
7781            self.write_space();
7782            self.generate_data_type(&col.data_type)?;
7783        }
7784        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7785        for constraint in &col.constraints {
7786            if let ColumnConstraint::Path(path_expr) = constraint {
7787                self.write_space();
7788                self.write_keyword("PATH");
7789                self.write_space();
7790                self.generate_expression(path_expr)?;
7791            }
7792        }
7793        Ok(())
7794    }
7795
7796    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7797        // Check if this is a TSQL computed column (no data type)
7798        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7799            && col
7800                .constraints
7801                .iter()
7802                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7803        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7804        let omit_computed_type = !self.config.computed_column_with_type
7805            && col
7806                .constraints
7807                .iter()
7808                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7809
7810        // Check if this is a partition column spec (no data type, type is Unknown)
7811        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7812        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7813
7814        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7815        // Also check the no_type flag for SQLite columns without types
7816        let has_no_type = col.no_type
7817            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7818                && col.constraints.is_empty());
7819
7820        self.generate_identifier(&col.name)?;
7821
7822        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7823        let serial_expansion = if matches!(
7824            self.config.dialect,
7825            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7826        ) {
7827            if let DataType::Custom { ref name } = col.data_type {
7828                match name.to_uppercase().as_str() {
7829                    "SERIAL" => Some("INT"),
7830                    "BIGSERIAL" => Some("BIGINT"),
7831                    "SMALLSERIAL" => Some("SMALLINT"),
7832                    _ => None,
7833                }
7834            } else {
7835                None
7836            }
7837        } else {
7838            None
7839        };
7840
7841        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7842        {
7843            self.write_space();
7844            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7845            // since ClickHouse uses explicit Nullable() in its type system.
7846            let saved_nullable_depth = self.clickhouse_nullable_depth;
7847            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7848                self.clickhouse_nullable_depth = -1;
7849            }
7850            if let Some(int_type) = serial_expansion {
7851                // SERIAL -> INT (+ constraints added below)
7852                self.write_keyword(int_type);
7853            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7854                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7855                let unsigned_type = match &col.data_type {
7856                    DataType::Int { .. } => Some("UINTEGER"),
7857                    DataType::BigInt { .. } => Some("UBIGINT"),
7858                    DataType::SmallInt { .. } => Some("USMALLINT"),
7859                    DataType::TinyInt { .. } => Some("UTINYINT"),
7860                    _ => None,
7861                };
7862                if let Some(utype) = unsigned_type {
7863                    self.write_keyword(utype);
7864                } else {
7865                    self.generate_data_type(&col.data_type)?;
7866                }
7867            } else {
7868                self.generate_data_type(&col.data_type)?;
7869            }
7870            self.clickhouse_nullable_depth = saved_nullable_depth;
7871        }
7872
7873        // MySQL type modifiers (must come right after data type)
7874        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7875        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7876            self.write_space();
7877            self.write_keyword("UNSIGNED");
7878        }
7879        if col.zerofill {
7880            self.write_space();
7881            self.write_keyword("ZEROFILL");
7882        }
7883
7884        // Teradata column attributes (must come right after data type, in specific order)
7885        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7886
7887        if let Some(ref charset) = col.character_set {
7888            self.write_space();
7889            self.write_keyword("CHARACTER SET");
7890            self.write_space();
7891            self.write(charset);
7892        }
7893
7894        if col.uppercase {
7895            self.write_space();
7896            self.write_keyword("UPPERCASE");
7897        }
7898
7899        if let Some(casespecific) = col.casespecific {
7900            self.write_space();
7901            if casespecific {
7902                self.write_keyword("CASESPECIFIC");
7903            } else {
7904                self.write_keyword("NOT CASESPECIFIC");
7905            }
7906        }
7907
7908        if let Some(ref format) = col.format {
7909            self.write_space();
7910            self.write_keyword("FORMAT");
7911            self.write(" '");
7912            self.write(format);
7913            self.write("'");
7914        }
7915
7916        if let Some(ref title) = col.title {
7917            self.write_space();
7918            self.write_keyword("TITLE");
7919            self.write(" '");
7920            self.write(title);
7921            self.write("'");
7922        }
7923
7924        if let Some(length) = col.inline_length {
7925            self.write_space();
7926            self.write_keyword("INLINE LENGTH");
7927            self.write(" ");
7928            self.write(&length.to_string());
7929        }
7930
7931        if let Some(ref compress) = col.compress {
7932            self.write_space();
7933            self.write_keyword("COMPRESS");
7934            if !compress.is_empty() {
7935                // Single string literal: output without parentheses (Teradata syntax)
7936                if compress.len() == 1 {
7937                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7938                        self.write_space();
7939                        self.generate_expression(&compress[0])?;
7940                    } else {
7941                        self.write(" (");
7942                        self.generate_expression(&compress[0])?;
7943                        self.write(")");
7944                    }
7945                } else {
7946                    self.write(" (");
7947                    for (i, val) in compress.iter().enumerate() {
7948                        if i > 0 {
7949                            self.write(", ");
7950                        }
7951                        self.generate_expression(val)?;
7952                    }
7953                    self.write(")");
7954                }
7955            }
7956        }
7957
7958        // Column constraints - output in original order if constraint_order is populated
7959        // Otherwise fall back to legacy fixed order for backward compatibility
7960        if !col.constraint_order.is_empty() {
7961            // Use constraint_order for original ordering
7962            // Track indices for constraints stored in the constraints Vec
7963            let mut references_idx = 0;
7964            let mut check_idx = 0;
7965            let mut generated_idx = 0;
7966            let mut collate_idx = 0;
7967            let mut comment_idx = 0;
7968            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7969            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7970            let defer_not_null_after_identity = false;
7971            let mut pending_not_null_after_identity = false;
7972
7973            for constraint_type in &col.constraint_order {
7974                match constraint_type {
7975                    ConstraintType::PrimaryKey => {
7976                        // Materialize doesn't support PRIMARY KEY column constraints
7977                        if col.primary_key
7978                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7979                        {
7980                            if let Some(ref cname) = col.primary_key_constraint_name {
7981                                self.write_space();
7982                                self.write_keyword("CONSTRAINT");
7983                                self.write_space();
7984                                self.write(cname);
7985                            }
7986                            self.write_space();
7987                            self.write_keyword("PRIMARY KEY");
7988                            if let Some(ref order) = col.primary_key_order {
7989                                self.write_space();
7990                                match order {
7991                                    SortOrder::Asc => self.write_keyword("ASC"),
7992                                    SortOrder::Desc => self.write_keyword("DESC"),
7993                                }
7994                            }
7995                        }
7996                    }
7997                    ConstraintType::Unique => {
7998                        if col.unique {
7999                            if let Some(ref cname) = col.unique_constraint_name {
8000                                self.write_space();
8001                                self.write_keyword("CONSTRAINT");
8002                                self.write_space();
8003                                self.write(cname);
8004                            }
8005                            self.write_space();
8006                            self.write_keyword("UNIQUE");
8007                            // PostgreSQL 15+: NULLS NOT DISTINCT
8008                            if col.unique_nulls_not_distinct {
8009                                self.write(" NULLS NOT DISTINCT");
8010                            }
8011                        }
8012                    }
8013                    ConstraintType::NotNull => {
8014                        if col.nullable == Some(false) {
8015                            if defer_not_null_after_identity {
8016                                pending_not_null_after_identity = true;
8017                                continue;
8018                            }
8019                            if let Some(ref cname) = col.not_null_constraint_name {
8020                                self.write_space();
8021                                self.write_keyword("CONSTRAINT");
8022                                self.write_space();
8023                                self.write(cname);
8024                            }
8025                            self.write_space();
8026                            self.write_keyword("NOT NULL");
8027                        }
8028                    }
8029                    ConstraintType::Null => {
8030                        if col.nullable == Some(true) {
8031                            self.write_space();
8032                            self.write_keyword("NULL");
8033                        }
8034                    }
8035                    ConstraintType::Default => {
8036                        if let Some(ref default) = col.default {
8037                            self.write_space();
8038                            self.write_keyword("DEFAULT");
8039                            self.write_space();
8040                            self.generate_expression(default)?;
8041                        }
8042                    }
8043                    ConstraintType::AutoIncrement => {
8044                        if col.auto_increment {
8045                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8046                            if matches!(
8047                                self.config.dialect,
8048                                Some(crate::dialects::DialectType::DuckDB)
8049                            ) {
8050                                // Skip - DuckDB uses sequences or rowid instead
8051                            } else if matches!(
8052                                self.config.dialect,
8053                                Some(crate::dialects::DialectType::Materialize)
8054                            ) {
8055                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8056                                if !matches!(col.nullable, Some(false)) {
8057                                    self.write_space();
8058                                    self.write_keyword("NOT NULL");
8059                                }
8060                            } else if matches!(
8061                                self.config.dialect,
8062                                Some(crate::dialects::DialectType::PostgreSQL)
8063                            ) {
8064                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8065                                self.write_space();
8066                                self.generate_auto_increment_keyword(col)?;
8067                            } else {
8068                                self.write_space();
8069                                self.generate_auto_increment_keyword(col)?;
8070                                if pending_not_null_after_identity {
8071                                    self.write_space();
8072                                    self.write_keyword("NOT NULL");
8073                                    pending_not_null_after_identity = false;
8074                                }
8075                            }
8076                        } // close else for DuckDB skip
8077                    }
8078                    ConstraintType::References => {
8079                        // Find next References constraint
8080                        while references_idx < col.constraints.len() {
8081                            if let ColumnConstraint::References(fk_ref) =
8082                                &col.constraints[references_idx]
8083                            {
8084                                // CONSTRAINT name if present
8085                                if let Some(ref name) = fk_ref.constraint_name {
8086                                    self.write_space();
8087                                    self.write_keyword("CONSTRAINT");
8088                                    self.write_space();
8089                                    self.write(name);
8090                                }
8091                                self.write_space();
8092                                if fk_ref.has_foreign_key_keywords {
8093                                    self.write_keyword("FOREIGN KEY");
8094                                    self.write_space();
8095                                }
8096                                self.write_keyword("REFERENCES");
8097                                self.write_space();
8098                                self.generate_table(&fk_ref.table)?;
8099                                if !fk_ref.columns.is_empty() {
8100                                    self.write(" (");
8101                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8102                                        if i > 0 {
8103                                            self.write(", ");
8104                                        }
8105                                        self.generate_identifier(c)?;
8106                                    }
8107                                    self.write(")");
8108                                }
8109                                self.generate_referential_actions(fk_ref)?;
8110                                references_idx += 1;
8111                                break;
8112                            }
8113                            references_idx += 1;
8114                        }
8115                    }
8116                    ConstraintType::Check => {
8117                        // Find next Check constraint
8118                        while check_idx < col.constraints.len() {
8119                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8120                                // Output CONSTRAINT name if present (only for first CHECK)
8121                                if check_idx == 0 {
8122                                    if let Some(ref cname) = col.check_constraint_name {
8123                                        self.write_space();
8124                                        self.write_keyword("CONSTRAINT");
8125                                        self.write_space();
8126                                        self.write(cname);
8127                                    }
8128                                }
8129                                self.write_space();
8130                                self.write_keyword("CHECK");
8131                                self.write(" (");
8132                                self.generate_expression(expr)?;
8133                                self.write(")");
8134                                check_idx += 1;
8135                                break;
8136                            }
8137                            check_idx += 1;
8138                        }
8139                    }
8140                    ConstraintType::GeneratedAsIdentity => {
8141                        // Find next GeneratedAsIdentity constraint
8142                        while generated_idx < col.constraints.len() {
8143                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8144                                &col.constraints[generated_idx]
8145                            {
8146                                self.write_space();
8147                                // Redshift uses IDENTITY(start, increment) syntax
8148                                if matches!(
8149                                    self.config.dialect,
8150                                    Some(crate::dialects::DialectType::Redshift)
8151                                ) {
8152                                    self.write_keyword("IDENTITY");
8153                                    self.write("(");
8154                                    if let Some(ref start) = gen.start {
8155                                        self.generate_expression(start)?;
8156                                    } else {
8157                                        self.write("0");
8158                                    }
8159                                    self.write(", ");
8160                                    if let Some(ref incr) = gen.increment {
8161                                        self.generate_expression(incr)?;
8162                                    } else {
8163                                        self.write("1");
8164                                    }
8165                                    self.write(")");
8166                                } else {
8167                                    self.write_keyword("GENERATED");
8168                                    if gen.always {
8169                                        self.write_space();
8170                                        self.write_keyword("ALWAYS");
8171                                    } else {
8172                                        self.write_space();
8173                                        self.write_keyword("BY DEFAULT");
8174                                        if gen.on_null {
8175                                            self.write_space();
8176                                            self.write_keyword("ON NULL");
8177                                        }
8178                                    }
8179                                    self.write_space();
8180                                    self.write_keyword("AS IDENTITY");
8181
8182                                    let has_options = gen.start.is_some()
8183                                        || gen.increment.is_some()
8184                                        || gen.minvalue.is_some()
8185                                        || gen.maxvalue.is_some()
8186                                        || gen.cycle.is_some();
8187                                    if has_options {
8188                                        self.write(" (");
8189                                        let mut first = true;
8190                                        if let Some(ref start) = gen.start {
8191                                            if !first {
8192                                                self.write(" ");
8193                                            }
8194                                            first = false;
8195                                            self.write_keyword("START WITH");
8196                                            self.write_space();
8197                                            self.generate_expression(start)?;
8198                                        }
8199                                        if let Some(ref incr) = gen.increment {
8200                                            if !first {
8201                                                self.write(" ");
8202                                            }
8203                                            first = false;
8204                                            self.write_keyword("INCREMENT BY");
8205                                            self.write_space();
8206                                            self.generate_expression(incr)?;
8207                                        }
8208                                        if let Some(ref minv) = gen.minvalue {
8209                                            if !first {
8210                                                self.write(" ");
8211                                            }
8212                                            first = false;
8213                                            self.write_keyword("MINVALUE");
8214                                            self.write_space();
8215                                            self.generate_expression(minv)?;
8216                                        }
8217                                        if let Some(ref maxv) = gen.maxvalue {
8218                                            if !first {
8219                                                self.write(" ");
8220                                            }
8221                                            first = false;
8222                                            self.write_keyword("MAXVALUE");
8223                                            self.write_space();
8224                                            self.generate_expression(maxv)?;
8225                                        }
8226                                        if let Some(cycle) = gen.cycle {
8227                                            if !first {
8228                                                self.write(" ");
8229                                            }
8230                                            if cycle {
8231                                                self.write_keyword("CYCLE");
8232                                            } else {
8233                                                self.write_keyword("NO CYCLE");
8234                                            }
8235                                        }
8236                                        self.write(")");
8237                                    }
8238                                }
8239                                generated_idx += 1;
8240                                break;
8241                            }
8242                            generated_idx += 1;
8243                        }
8244                    }
8245                    ConstraintType::Collate => {
8246                        // Find next Collate constraint
8247                        while collate_idx < col.constraints.len() {
8248                            if let ColumnConstraint::Collate(collation) =
8249                                &col.constraints[collate_idx]
8250                            {
8251                                self.write_space();
8252                                self.write_keyword("COLLATE");
8253                                self.write_space();
8254                                self.generate_identifier(collation)?;
8255                                collate_idx += 1;
8256                                break;
8257                            }
8258                            collate_idx += 1;
8259                        }
8260                    }
8261                    ConstraintType::Comment => {
8262                        // Find next Comment constraint
8263                        while comment_idx < col.constraints.len() {
8264                            if let ColumnConstraint::Comment(comment) =
8265                                &col.constraints[comment_idx]
8266                            {
8267                                self.write_space();
8268                                self.write_keyword("COMMENT");
8269                                self.write_space();
8270                                self.generate_string_literal(comment)?;
8271                                comment_idx += 1;
8272                                break;
8273                            }
8274                            comment_idx += 1;
8275                        }
8276                    }
8277                    ConstraintType::Tags => {
8278                        // Find next Tags constraint (Snowflake)
8279                        for constraint in &col.constraints {
8280                            if let ColumnConstraint::Tags(tags) = constraint {
8281                                self.write_space();
8282                                self.write_keyword("TAG");
8283                                self.write(" (");
8284                                for (i, expr) in tags.expressions.iter().enumerate() {
8285                                    if i > 0 {
8286                                        self.write(", ");
8287                                    }
8288                                    self.generate_expression(expr)?;
8289                                }
8290                                self.write(")");
8291                                break;
8292                            }
8293                        }
8294                    }
8295                    ConstraintType::ComputedColumn => {
8296                        // Find next ComputedColumn constraint
8297                        for constraint in &col.constraints {
8298                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8299                                self.write_space();
8300                                self.generate_computed_column_inline(cc)?;
8301                                break;
8302                            }
8303                        }
8304                    }
8305                    ConstraintType::GeneratedAsRow => {
8306                        // Find next GeneratedAsRow constraint
8307                        for constraint in &col.constraints {
8308                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8309                                self.write_space();
8310                                self.generate_generated_as_row_inline(gar)?;
8311                                break;
8312                            }
8313                        }
8314                    }
8315                    ConstraintType::OnUpdate => {
8316                        if let Some(ref expr) = col.on_update {
8317                            self.write_space();
8318                            self.write_keyword("ON UPDATE");
8319                            self.write_space();
8320                            self.generate_expression(expr)?;
8321                        }
8322                    }
8323                    ConstraintType::Encode => {
8324                        if let Some(ref encoding) = col.encoding {
8325                            self.write_space();
8326                            self.write_keyword("ENCODE");
8327                            self.write_space();
8328                            self.write(encoding);
8329                        }
8330                    }
8331                    ConstraintType::Path => {
8332                        // Find next Path constraint
8333                        for constraint in &col.constraints {
8334                            if let ColumnConstraint::Path(path_expr) = constraint {
8335                                self.write_space();
8336                                self.write_keyword("PATH");
8337                                self.write_space();
8338                                self.generate_expression(path_expr)?;
8339                                break;
8340                            }
8341                        }
8342                    }
8343                }
8344            }
8345            if pending_not_null_after_identity {
8346                self.write_space();
8347                self.write_keyword("NOT NULL");
8348            }
8349        } else {
8350            // Legacy fixed order for backward compatibility
8351            if col.primary_key {
8352                self.write_space();
8353                self.write_keyword("PRIMARY KEY");
8354                if let Some(ref order) = col.primary_key_order {
8355                    self.write_space();
8356                    match order {
8357                        SortOrder::Asc => self.write_keyword("ASC"),
8358                        SortOrder::Desc => self.write_keyword("DESC"),
8359                    }
8360                }
8361            }
8362
8363            if col.unique {
8364                self.write_space();
8365                self.write_keyword("UNIQUE");
8366                // PostgreSQL 15+: NULLS NOT DISTINCT
8367                if col.unique_nulls_not_distinct {
8368                    self.write(" NULLS NOT DISTINCT");
8369                }
8370            }
8371
8372            match col.nullable {
8373                Some(false) => {
8374                    self.write_space();
8375                    self.write_keyword("NOT NULL");
8376                }
8377                Some(true) => {
8378                    self.write_space();
8379                    self.write_keyword("NULL");
8380                }
8381                None => {}
8382            }
8383
8384            if let Some(ref default) = col.default {
8385                self.write_space();
8386                self.write_keyword("DEFAULT");
8387                self.write_space();
8388                self.generate_expression(default)?;
8389            }
8390
8391            if col.auto_increment {
8392                self.write_space();
8393                self.generate_auto_increment_keyword(col)?;
8394            }
8395
8396            // Column-level constraints from Vec
8397            for constraint in &col.constraints {
8398                match constraint {
8399                    ColumnConstraint::References(fk_ref) => {
8400                        self.write_space();
8401                        if fk_ref.has_foreign_key_keywords {
8402                            self.write_keyword("FOREIGN KEY");
8403                            self.write_space();
8404                        }
8405                        self.write_keyword("REFERENCES");
8406                        self.write_space();
8407                        self.generate_table(&fk_ref.table)?;
8408                        if !fk_ref.columns.is_empty() {
8409                            self.write(" (");
8410                            for (i, c) in fk_ref.columns.iter().enumerate() {
8411                                if i > 0 {
8412                                    self.write(", ");
8413                                }
8414                                self.generate_identifier(c)?;
8415                            }
8416                            self.write(")");
8417                        }
8418                        self.generate_referential_actions(fk_ref)?;
8419                    }
8420                    ColumnConstraint::Check(expr) => {
8421                        self.write_space();
8422                        self.write_keyword("CHECK");
8423                        self.write(" (");
8424                        self.generate_expression(expr)?;
8425                        self.write(")");
8426                    }
8427                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8428                        self.write_space();
8429                        // Redshift uses IDENTITY(start, increment) syntax
8430                        if matches!(
8431                            self.config.dialect,
8432                            Some(crate::dialects::DialectType::Redshift)
8433                        ) {
8434                            self.write_keyword("IDENTITY");
8435                            self.write("(");
8436                            if let Some(ref start) = gen.start {
8437                                self.generate_expression(start)?;
8438                            } else {
8439                                self.write("0");
8440                            }
8441                            self.write(", ");
8442                            if let Some(ref incr) = gen.increment {
8443                                self.generate_expression(incr)?;
8444                            } else {
8445                                self.write("1");
8446                            }
8447                            self.write(")");
8448                        } else {
8449                            self.write_keyword("GENERATED");
8450                            if gen.always {
8451                                self.write_space();
8452                                self.write_keyword("ALWAYS");
8453                            } else {
8454                                self.write_space();
8455                                self.write_keyword("BY DEFAULT");
8456                                if gen.on_null {
8457                                    self.write_space();
8458                                    self.write_keyword("ON NULL");
8459                                }
8460                            }
8461                            self.write_space();
8462                            self.write_keyword("AS IDENTITY");
8463
8464                            let has_options = gen.start.is_some()
8465                                || gen.increment.is_some()
8466                                || gen.minvalue.is_some()
8467                                || gen.maxvalue.is_some()
8468                                || gen.cycle.is_some();
8469                            if has_options {
8470                                self.write(" (");
8471                                let mut first = true;
8472                                if let Some(ref start) = gen.start {
8473                                    if !first {
8474                                        self.write(" ");
8475                                    }
8476                                    first = false;
8477                                    self.write_keyword("START WITH");
8478                                    self.write_space();
8479                                    self.generate_expression(start)?;
8480                                }
8481                                if let Some(ref incr) = gen.increment {
8482                                    if !first {
8483                                        self.write(" ");
8484                                    }
8485                                    first = false;
8486                                    self.write_keyword("INCREMENT BY");
8487                                    self.write_space();
8488                                    self.generate_expression(incr)?;
8489                                }
8490                                if let Some(ref minv) = gen.minvalue {
8491                                    if !first {
8492                                        self.write(" ");
8493                                    }
8494                                    first = false;
8495                                    self.write_keyword("MINVALUE");
8496                                    self.write_space();
8497                                    self.generate_expression(minv)?;
8498                                }
8499                                if let Some(ref maxv) = gen.maxvalue {
8500                                    if !first {
8501                                        self.write(" ");
8502                                    }
8503                                    first = false;
8504                                    self.write_keyword("MAXVALUE");
8505                                    self.write_space();
8506                                    self.generate_expression(maxv)?;
8507                                }
8508                                if let Some(cycle) = gen.cycle {
8509                                    if !first {
8510                                        self.write(" ");
8511                                    }
8512                                    if cycle {
8513                                        self.write_keyword("CYCLE");
8514                                    } else {
8515                                        self.write_keyword("NO CYCLE");
8516                                    }
8517                                }
8518                                self.write(")");
8519                            }
8520                        }
8521                    }
8522                    ColumnConstraint::Collate(collation) => {
8523                        self.write_space();
8524                        self.write_keyword("COLLATE");
8525                        self.write_space();
8526                        self.generate_identifier(collation)?;
8527                    }
8528                    ColumnConstraint::Comment(comment) => {
8529                        self.write_space();
8530                        self.write_keyword("COMMENT");
8531                        self.write_space();
8532                        self.generate_string_literal(comment)?;
8533                    }
8534                    ColumnConstraint::Path(path_expr) => {
8535                        self.write_space();
8536                        self.write_keyword("PATH");
8537                        self.write_space();
8538                        self.generate_expression(path_expr)?;
8539                    }
8540                    _ => {} // Other constraints handled above
8541                }
8542            }
8543
8544            // Redshift: ENCODE encoding_type (legacy path)
8545            if let Some(ref encoding) = col.encoding {
8546                self.write_space();
8547                self.write_keyword("ENCODE");
8548                self.write_space();
8549                self.write(encoding);
8550            }
8551        }
8552
8553        // ClickHouse: CODEC(...)
8554        if let Some(ref codec) = col.codec {
8555            self.write_space();
8556            self.write_keyword("CODEC");
8557            self.write("(");
8558            self.write(codec);
8559            self.write(")");
8560        }
8561
8562        // ClickHouse: EPHEMERAL [expr]
8563        if let Some(ref ephemeral) = col.ephemeral {
8564            self.write_space();
8565            self.write_keyword("EPHEMERAL");
8566            if let Some(ref expr) = ephemeral {
8567                self.write_space();
8568                self.generate_expression(expr)?;
8569            }
8570        }
8571
8572        // ClickHouse: MATERIALIZED expr
8573        if let Some(ref mat_expr) = col.materialized_expr {
8574            self.write_space();
8575            self.write_keyword("MATERIALIZED");
8576            self.write_space();
8577            self.generate_expression(mat_expr)?;
8578        }
8579
8580        // ClickHouse: ALIAS expr
8581        if let Some(ref alias_expr) = col.alias_expr {
8582            self.write_space();
8583            self.write_keyword("ALIAS");
8584            self.write_space();
8585            self.generate_expression(alias_expr)?;
8586        }
8587
8588        // ClickHouse: TTL expr
8589        if let Some(ref ttl_expr) = col.ttl_expr {
8590            self.write_space();
8591            self.write_keyword("TTL");
8592            self.write_space();
8593            self.generate_expression(ttl_expr)?;
8594        }
8595
8596        // TSQL: NOT FOR REPLICATION
8597        if col.not_for_replication
8598            && matches!(
8599                self.config.dialect,
8600                Some(crate::dialects::DialectType::TSQL)
8601                    | Some(crate::dialects::DialectType::Fabric)
8602            )
8603        {
8604            self.write_space();
8605            self.write_keyword("NOT FOR REPLICATION");
8606        }
8607
8608        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8609        if !col.options.is_empty() {
8610            self.write_space();
8611            self.generate_options_clause(&col.options)?;
8612        }
8613
8614        // SQLite: Inline PRIMARY KEY from table constraint
8615        // This comes at the end, after all existing column constraints
8616        if !col.primary_key
8617            && self
8618                .sqlite_inline_pk_columns
8619                .contains(&col.name.name.to_lowercase())
8620        {
8621            self.write_space();
8622            self.write_keyword("PRIMARY KEY");
8623        }
8624
8625        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8626        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8627        if serial_expansion.is_some() {
8628            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8629                self.write_space();
8630                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8631            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8632                self.write_space();
8633                self.write_keyword("NOT NULL");
8634            }
8635        }
8636
8637        Ok(())
8638    }
8639
8640    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8641        match constraint {
8642            TableConstraint::PrimaryKey {
8643                name,
8644                columns,
8645                include_columns,
8646                modifiers,
8647                has_constraint_keyword,
8648            } => {
8649                if let Some(ref n) = name {
8650                    if *has_constraint_keyword {
8651                        self.write_keyword("CONSTRAINT");
8652                        self.write_space();
8653                        self.generate_identifier(n)?;
8654                        self.write_space();
8655                    }
8656                }
8657                self.write_keyword("PRIMARY KEY");
8658                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8659                if let Some(ref clustered) = modifiers.clustered {
8660                    self.write_space();
8661                    self.write_keyword(clustered);
8662                }
8663                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8664                if let Some(ref n) = name {
8665                    if !*has_constraint_keyword {
8666                        self.write_space();
8667                        self.generate_identifier(n)?;
8668                    }
8669                }
8670                self.write(" (");
8671                for (i, col) in columns.iter().enumerate() {
8672                    if i > 0 {
8673                        self.write(", ");
8674                    }
8675                    self.generate_identifier(col)?;
8676                }
8677                self.write(")");
8678                if !include_columns.is_empty() {
8679                    self.write_space();
8680                    self.write_keyword("INCLUDE");
8681                    self.write(" (");
8682                    for (i, col) in include_columns.iter().enumerate() {
8683                        if i > 0 {
8684                            self.write(", ");
8685                        }
8686                        self.generate_identifier(col)?;
8687                    }
8688                    self.write(")");
8689                }
8690                self.generate_constraint_modifiers(modifiers);
8691            }
8692            TableConstraint::Unique {
8693                name,
8694                columns,
8695                columns_parenthesized,
8696                modifiers,
8697                has_constraint_keyword,
8698                nulls_not_distinct,
8699            } => {
8700                if let Some(ref n) = name {
8701                    if *has_constraint_keyword {
8702                        self.write_keyword("CONSTRAINT");
8703                        self.write_space();
8704                        self.generate_identifier(n)?;
8705                        self.write_space();
8706                    }
8707                }
8708                self.write_keyword("UNIQUE");
8709                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8710                if let Some(ref clustered) = modifiers.clustered {
8711                    self.write_space();
8712                    self.write_keyword(clustered);
8713                }
8714                // PostgreSQL 15+: NULLS NOT DISTINCT
8715                if *nulls_not_distinct {
8716                    self.write(" NULLS NOT DISTINCT");
8717                }
8718                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8719                if let Some(ref n) = name {
8720                    if !*has_constraint_keyword {
8721                        self.write_space();
8722                        self.generate_identifier(n)?;
8723                    }
8724                }
8725                if *columns_parenthesized {
8726                    self.write(" (");
8727                    for (i, col) in columns.iter().enumerate() {
8728                        if i > 0 {
8729                            self.write(", ");
8730                        }
8731                        self.generate_identifier(col)?;
8732                    }
8733                    self.write(")");
8734                } else {
8735                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8736                    for col in columns.iter() {
8737                        self.write_space();
8738                        self.generate_identifier(col)?;
8739                    }
8740                }
8741                self.generate_constraint_modifiers(modifiers);
8742            }
8743            TableConstraint::ForeignKey {
8744                name,
8745                columns,
8746                references,
8747                on_delete,
8748                on_update,
8749                modifiers,
8750            } => {
8751                if let Some(ref n) = name {
8752                    self.write_keyword("CONSTRAINT");
8753                    self.write_space();
8754                    self.generate_identifier(n)?;
8755                    self.write_space();
8756                }
8757                self.write_keyword("FOREIGN KEY");
8758                self.write(" (");
8759                for (i, col) in columns.iter().enumerate() {
8760                    if i > 0 {
8761                        self.write(", ");
8762                    }
8763                    self.generate_identifier(col)?;
8764                }
8765                self.write(")");
8766                if let Some(ref refs) = references {
8767                    self.write(" ");
8768                    self.write_keyword("REFERENCES");
8769                    self.write_space();
8770                    self.generate_table(&refs.table)?;
8771                    if !refs.columns.is_empty() {
8772                        if self.config.pretty {
8773                            self.write(" (");
8774                            self.write_newline();
8775                            self.indent_level += 1;
8776                            for (i, col) in refs.columns.iter().enumerate() {
8777                                if i > 0 {
8778                                    self.write(",");
8779                                    self.write_newline();
8780                                }
8781                                self.write_indent();
8782                                self.generate_identifier(col)?;
8783                            }
8784                            self.indent_level -= 1;
8785                            self.write_newline();
8786                            self.write_indent();
8787                            self.write(")");
8788                        } else {
8789                            self.write(" (");
8790                            for (i, col) in refs.columns.iter().enumerate() {
8791                                if i > 0 {
8792                                    self.write(", ");
8793                                }
8794                                self.generate_identifier(col)?;
8795                            }
8796                            self.write(")");
8797                        }
8798                    }
8799                    self.generate_referential_actions(refs)?;
8800                } else {
8801                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8802                    if let Some(ref action) = on_delete {
8803                        self.write_space();
8804                        self.write_keyword("ON DELETE");
8805                        self.write_space();
8806                        self.generate_referential_action(action);
8807                    }
8808                    if let Some(ref action) = on_update {
8809                        self.write_space();
8810                        self.write_keyword("ON UPDATE");
8811                        self.write_space();
8812                        self.generate_referential_action(action);
8813                    }
8814                }
8815                self.generate_constraint_modifiers(modifiers);
8816            }
8817            TableConstraint::Check {
8818                name,
8819                expression,
8820                modifiers,
8821            } => {
8822                if let Some(ref n) = name {
8823                    self.write_keyword("CONSTRAINT");
8824                    self.write_space();
8825                    self.generate_identifier(n)?;
8826                    self.write_space();
8827                }
8828                self.write_keyword("CHECK");
8829                self.write(" (");
8830                self.generate_expression(expression)?;
8831                self.write(")");
8832                self.generate_constraint_modifiers(modifiers);
8833            }
8834            TableConstraint::Index {
8835                name,
8836                columns,
8837                kind,
8838                modifiers,
8839                use_key_keyword,
8840                expression,
8841                index_type,
8842                granularity,
8843            } => {
8844                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8845                if expression.is_some() {
8846                    self.write_keyword("INDEX");
8847                    if let Some(ref n) = name {
8848                        self.write_space();
8849                        self.generate_identifier(n)?;
8850                    }
8851                    if let Some(ref expr) = expression {
8852                        self.write_space();
8853                        self.generate_expression(expr)?;
8854                    }
8855                    if let Some(ref idx_type) = index_type {
8856                        self.write_space();
8857                        self.write_keyword("TYPE");
8858                        self.write_space();
8859                        self.generate_expression(idx_type)?;
8860                    }
8861                    if let Some(ref gran) = granularity {
8862                        self.write_space();
8863                        self.write_keyword("GRANULARITY");
8864                        self.write_space();
8865                        self.generate_expression(gran)?;
8866                    }
8867                } else {
8868                    // Standard INDEX syntax
8869                    // Determine the index keyword to use
8870                    // MySQL normalizes KEY to INDEX
8871                    use crate::dialects::DialectType;
8872                    let index_keyword = if *use_key_keyword
8873                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8874                    {
8875                        "KEY"
8876                    } else {
8877                        "INDEX"
8878                    };
8879
8880                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8881                    if let Some(ref k) = kind {
8882                        self.write_keyword(k);
8883                        // For UNIQUE, don't add INDEX/KEY keyword
8884                        if k != "UNIQUE" {
8885                            self.write_space();
8886                            self.write_keyword(index_keyword);
8887                        }
8888                    } else {
8889                        self.write_keyword(index_keyword);
8890                    }
8891
8892                    // Output USING before name if using_before_columns is true and there's no name
8893                    if modifiers.using_before_columns && name.is_none() {
8894                        if let Some(ref using) = modifiers.using {
8895                            self.write_space();
8896                            self.write_keyword("USING");
8897                            self.write_space();
8898                            self.write_keyword(using);
8899                        }
8900                    }
8901
8902                    // Output index name if present
8903                    if let Some(ref n) = name {
8904                        self.write_space();
8905                        self.generate_identifier(n)?;
8906                    }
8907
8908                    // Output USING after name but before columns if using_before_columns and there's a name
8909                    if modifiers.using_before_columns && name.is_some() {
8910                        if let Some(ref using) = modifiers.using {
8911                            self.write_space();
8912                            self.write_keyword("USING");
8913                            self.write_space();
8914                            self.write_keyword(using);
8915                        }
8916                    }
8917
8918                    // Output columns
8919                    self.write(" (");
8920                    for (i, col) in columns.iter().enumerate() {
8921                        if i > 0 {
8922                            self.write(", ");
8923                        }
8924                        self.generate_identifier(col)?;
8925                    }
8926                    self.write(")");
8927
8928                    // Output USING after columns if not using_before_columns
8929                    if !modifiers.using_before_columns {
8930                        if let Some(ref using) = modifiers.using {
8931                            self.write_space();
8932                            self.write_keyword("USING");
8933                            self.write_space();
8934                            self.write_keyword(using);
8935                        }
8936                    }
8937
8938                    // Output other constraint modifiers (but skip USING since we already handled it)
8939                    self.generate_constraint_modifiers_without_using(modifiers);
8940                }
8941            }
8942            TableConstraint::Projection { name, expression } => {
8943                // ClickHouse: PROJECTION name (SELECT ...)
8944                self.write_keyword("PROJECTION");
8945                self.write_space();
8946                self.generate_identifier(name)?;
8947                self.write(" (");
8948                self.generate_expression(expression)?;
8949                self.write(")");
8950            }
8951            TableConstraint::Like { source, options } => {
8952                self.write_keyword("LIKE");
8953                self.write_space();
8954                self.generate_table(source)?;
8955                for (action, prop) in options {
8956                    self.write_space();
8957                    match action {
8958                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8959                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8960                    }
8961                    self.write_space();
8962                    self.write_keyword(prop);
8963                }
8964            }
8965            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8966                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8967                self.write(" (");
8968                self.generate_identifier(start_col)?;
8969                self.write(", ");
8970                self.generate_identifier(end_col)?;
8971                self.write(")");
8972            }
8973            TableConstraint::Exclude {
8974                name,
8975                using,
8976                elements,
8977                include_columns,
8978                where_clause,
8979                with_params,
8980                using_index_tablespace,
8981                modifiers: _,
8982            } => {
8983                if let Some(ref n) = name {
8984                    self.write_keyword("CONSTRAINT");
8985                    self.write_space();
8986                    self.generate_identifier(n)?;
8987                    self.write_space();
8988                }
8989                self.write_keyword("EXCLUDE");
8990                if let Some(ref method) = using {
8991                    self.write_space();
8992                    self.write_keyword("USING");
8993                    self.write_space();
8994                    self.write(method);
8995                    self.write("(");
8996                } else {
8997                    self.write(" (");
8998                }
8999                for (i, elem) in elements.iter().enumerate() {
9000                    if i > 0 {
9001                        self.write(", ");
9002                    }
9003                    self.write(&elem.expression);
9004                    self.write_space();
9005                    self.write_keyword("WITH");
9006                    self.write_space();
9007                    self.write(&elem.operator);
9008                }
9009                self.write(")");
9010                if !include_columns.is_empty() {
9011                    self.write_space();
9012                    self.write_keyword("INCLUDE");
9013                    self.write(" (");
9014                    for (i, col) in include_columns.iter().enumerate() {
9015                        if i > 0 {
9016                            self.write(", ");
9017                        }
9018                        self.generate_identifier(col)?;
9019                    }
9020                    self.write(")");
9021                }
9022                if !with_params.is_empty() {
9023                    self.write_space();
9024                    self.write_keyword("WITH");
9025                    self.write(" (");
9026                    for (i, (key, val)) in with_params.iter().enumerate() {
9027                        if i > 0 {
9028                            self.write(", ");
9029                        }
9030                        self.write(key);
9031                        self.write("=");
9032                        self.write(val);
9033                    }
9034                    self.write(")");
9035                }
9036                if let Some(ref tablespace) = using_index_tablespace {
9037                    self.write_space();
9038                    self.write_keyword("USING INDEX TABLESPACE");
9039                    self.write_space();
9040                    self.write(tablespace);
9041                }
9042                if let Some(ref where_expr) = where_clause {
9043                    self.write_space();
9044                    self.write_keyword("WHERE");
9045                    self.write(" (");
9046                    self.generate_expression(where_expr)?;
9047                    self.write(")");
9048                }
9049            }
9050            TableConstraint::Tags(tags) => {
9051                self.write_keyword("TAG");
9052                self.write(" (");
9053                for (i, expr) in tags.expressions.iter().enumerate() {
9054                    if i > 0 {
9055                        self.write(", ");
9056                    }
9057                    self.generate_expression(expr)?;
9058                }
9059                self.write(")");
9060            }
9061            TableConstraint::InitiallyDeferred { deferred } => {
9062                self.write_keyword("INITIALLY");
9063                self.write_space();
9064                if *deferred {
9065                    self.write_keyword("DEFERRED");
9066                } else {
9067                    self.write_keyword("IMMEDIATE");
9068                }
9069            }
9070        }
9071        Ok(())
9072    }
9073
9074    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9075        // Output USING BTREE/HASH (MySQL) - comes first
9076        if let Some(using) = &modifiers.using {
9077            self.write_space();
9078            self.write_keyword("USING");
9079            self.write_space();
9080            self.write_keyword(using);
9081        }
9082        // Output ENFORCED/NOT ENFORCED
9083        if let Some(enforced) = modifiers.enforced {
9084            self.write_space();
9085            if enforced {
9086                self.write_keyword("ENFORCED");
9087            } else {
9088                self.write_keyword("NOT ENFORCED");
9089            }
9090        }
9091        // Output DEFERRABLE/NOT DEFERRABLE
9092        if let Some(deferrable) = modifiers.deferrable {
9093            self.write_space();
9094            if deferrable {
9095                self.write_keyword("DEFERRABLE");
9096            } else {
9097                self.write_keyword("NOT DEFERRABLE");
9098            }
9099        }
9100        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9101        if let Some(initially_deferred) = modifiers.initially_deferred {
9102            self.write_space();
9103            if initially_deferred {
9104                self.write_keyword("INITIALLY DEFERRED");
9105            } else {
9106                self.write_keyword("INITIALLY IMMEDIATE");
9107            }
9108        }
9109        // Output NORELY
9110        if modifiers.norely {
9111            self.write_space();
9112            self.write_keyword("NORELY");
9113        }
9114        // Output RELY
9115        if modifiers.rely {
9116            self.write_space();
9117            self.write_keyword("RELY");
9118        }
9119        // Output NOT VALID (PostgreSQL)
9120        if modifiers.not_valid {
9121            self.write_space();
9122            self.write_keyword("NOT VALID");
9123        }
9124        // Output ON CONFLICT (SQLite)
9125        if let Some(on_conflict) = &modifiers.on_conflict {
9126            self.write_space();
9127            self.write_keyword("ON CONFLICT");
9128            self.write_space();
9129            self.write_keyword(on_conflict);
9130        }
9131        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9132        if !modifiers.with_options.is_empty() {
9133            self.write_space();
9134            self.write_keyword("WITH");
9135            self.write(" (");
9136            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9137                if i > 0 {
9138                    self.write(", ");
9139                }
9140                self.write(key);
9141                self.write("=");
9142                self.write(value);
9143            }
9144            self.write(")");
9145        }
9146        // Output TSQL ON filegroup
9147        if let Some(ref fg) = modifiers.on_filegroup {
9148            self.write_space();
9149            self.write_keyword("ON");
9150            self.write_space();
9151            let _ = self.generate_identifier(fg);
9152        }
9153    }
9154
9155    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9156    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9157        // Output ENFORCED/NOT ENFORCED
9158        if let Some(enforced) = modifiers.enforced {
9159            self.write_space();
9160            if enforced {
9161                self.write_keyword("ENFORCED");
9162            } else {
9163                self.write_keyword("NOT ENFORCED");
9164            }
9165        }
9166        // Output DEFERRABLE/NOT DEFERRABLE
9167        if let Some(deferrable) = modifiers.deferrable {
9168            self.write_space();
9169            if deferrable {
9170                self.write_keyword("DEFERRABLE");
9171            } else {
9172                self.write_keyword("NOT DEFERRABLE");
9173            }
9174        }
9175        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9176        if let Some(initially_deferred) = modifiers.initially_deferred {
9177            self.write_space();
9178            if initially_deferred {
9179                self.write_keyword("INITIALLY DEFERRED");
9180            } else {
9181                self.write_keyword("INITIALLY IMMEDIATE");
9182            }
9183        }
9184        // Output NORELY
9185        if modifiers.norely {
9186            self.write_space();
9187            self.write_keyword("NORELY");
9188        }
9189        // Output RELY
9190        if modifiers.rely {
9191            self.write_space();
9192            self.write_keyword("RELY");
9193        }
9194        // Output NOT VALID (PostgreSQL)
9195        if modifiers.not_valid {
9196            self.write_space();
9197            self.write_keyword("NOT VALID");
9198        }
9199        // Output ON CONFLICT (SQLite)
9200        if let Some(on_conflict) = &modifiers.on_conflict {
9201            self.write_space();
9202            self.write_keyword("ON CONFLICT");
9203            self.write_space();
9204            self.write_keyword(on_conflict);
9205        }
9206        // Output MySQL index-specific modifiers
9207        self.generate_index_specific_modifiers(modifiers);
9208    }
9209
9210    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9211    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9212        if let Some(ref comment) = modifiers.comment {
9213            self.write_space();
9214            self.write_keyword("COMMENT");
9215            self.write(" '");
9216            self.write(comment);
9217            self.write("'");
9218        }
9219        if let Some(visible) = modifiers.visible {
9220            self.write_space();
9221            if visible {
9222                self.write_keyword("VISIBLE");
9223            } else {
9224                self.write_keyword("INVISIBLE");
9225            }
9226        }
9227        if let Some(ref attr) = modifiers.engine_attribute {
9228            self.write_space();
9229            self.write_keyword("ENGINE_ATTRIBUTE");
9230            self.write(" = '");
9231            self.write(attr);
9232            self.write("'");
9233        }
9234        if let Some(ref parser) = modifiers.with_parser {
9235            self.write_space();
9236            self.write_keyword("WITH PARSER");
9237            self.write_space();
9238            self.write(parser);
9239        }
9240    }
9241
9242    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9243        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9244        if !fk_ref.match_after_actions {
9245            if let Some(ref match_type) = fk_ref.match_type {
9246                self.write_space();
9247                self.write_keyword("MATCH");
9248                self.write_space();
9249                match match_type {
9250                    MatchType::Full => self.write_keyword("FULL"),
9251                    MatchType::Partial => self.write_keyword("PARTIAL"),
9252                    MatchType::Simple => self.write_keyword("SIMPLE"),
9253                }
9254            }
9255        }
9256
9257        // Output ON UPDATE and ON DELETE in the original order
9258        if fk_ref.on_update_first {
9259            if let Some(ref action) = fk_ref.on_update {
9260                self.write_space();
9261                self.write_keyword("ON UPDATE");
9262                self.write_space();
9263                self.generate_referential_action(action);
9264            }
9265            if let Some(ref action) = fk_ref.on_delete {
9266                self.write_space();
9267                self.write_keyword("ON DELETE");
9268                self.write_space();
9269                self.generate_referential_action(action);
9270            }
9271        } else {
9272            if let Some(ref action) = fk_ref.on_delete {
9273                self.write_space();
9274                self.write_keyword("ON DELETE");
9275                self.write_space();
9276                self.generate_referential_action(action);
9277            }
9278            if let Some(ref action) = fk_ref.on_update {
9279                self.write_space();
9280                self.write_keyword("ON UPDATE");
9281                self.write_space();
9282                self.generate_referential_action(action);
9283            }
9284        }
9285
9286        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9287        if fk_ref.match_after_actions {
9288            if let Some(ref match_type) = fk_ref.match_type {
9289                self.write_space();
9290                self.write_keyword("MATCH");
9291                self.write_space();
9292                match match_type {
9293                    MatchType::Full => self.write_keyword("FULL"),
9294                    MatchType::Partial => self.write_keyword("PARTIAL"),
9295                    MatchType::Simple => self.write_keyword("SIMPLE"),
9296                }
9297            }
9298        }
9299
9300        // DEFERRABLE / NOT DEFERRABLE
9301        if let Some(deferrable) = fk_ref.deferrable {
9302            self.write_space();
9303            if deferrable {
9304                self.write_keyword("DEFERRABLE");
9305            } else {
9306                self.write_keyword("NOT DEFERRABLE");
9307            }
9308        }
9309
9310        Ok(())
9311    }
9312
9313    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9314        match action {
9315            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9316            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9317            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9318            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9319            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9320        }
9321    }
9322
9323    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9324        // Athena: DROP TABLE uses Hive engine (backticks)
9325        let saved_athena_hive_context = self.athena_hive_context;
9326        if matches!(
9327            self.config.dialect,
9328            Some(crate::dialects::DialectType::Athena)
9329        ) {
9330            self.athena_hive_context = true;
9331        }
9332
9333        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9334        for comment in &dt.leading_comments {
9335            self.write_formatted_comment(comment);
9336            self.write_space();
9337        }
9338        self.write_keyword("DROP TABLE");
9339
9340        if dt.if_exists {
9341            self.write_space();
9342            self.write_keyword("IF EXISTS");
9343        }
9344
9345        self.write_space();
9346        for (i, table) in dt.names.iter().enumerate() {
9347            if i > 0 {
9348                self.write(", ");
9349            }
9350            self.generate_table(table)?;
9351        }
9352
9353        if dt.cascade_constraints {
9354            self.write_space();
9355            self.write_keyword("CASCADE CONSTRAINTS");
9356        } else if dt.cascade {
9357            self.write_space();
9358            self.write_keyword("CASCADE");
9359        }
9360
9361        if dt.purge {
9362            self.write_space();
9363            self.write_keyword("PURGE");
9364        }
9365
9366        // Restore Athena Hive context
9367        self.athena_hive_context = saved_athena_hive_context;
9368
9369        Ok(())
9370    }
9371
9372    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9373        // Athena: ALTER TABLE uses Hive engine (backticks)
9374        let saved_athena_hive_context = self.athena_hive_context;
9375        if matches!(
9376            self.config.dialect,
9377            Some(crate::dialects::DialectType::Athena)
9378        ) {
9379            self.athena_hive_context = true;
9380        }
9381
9382        self.write_keyword("ALTER TABLE");
9383        if at.if_exists {
9384            self.write_space();
9385            self.write_keyword("IF EXISTS");
9386        }
9387        self.write_space();
9388        self.generate_table(&at.name)?;
9389
9390        // ClickHouse: ON CLUSTER clause
9391        if let Some(ref on_cluster) = at.on_cluster {
9392            self.write_space();
9393            self.generate_on_cluster(on_cluster)?;
9394        }
9395
9396        // Hive: PARTITION(key=value, ...) clause
9397        if let Some(ref partition) = at.partition {
9398            self.write_space();
9399            self.write_keyword("PARTITION");
9400            self.write("(");
9401            for (i, (key, value)) in partition.iter().enumerate() {
9402                if i > 0 {
9403                    self.write(", ");
9404                }
9405                self.generate_identifier(key)?;
9406                self.write(" = ");
9407                self.generate_expression(value)?;
9408            }
9409            self.write(")");
9410        }
9411
9412        // TSQL: WITH CHECK / WITH NOCHECK modifier
9413        if let Some(ref with_check) = at.with_check {
9414            self.write_space();
9415            self.write_keyword(with_check);
9416        }
9417
9418        if self.config.pretty {
9419            // In pretty mode, format actions with newlines and indentation
9420            self.write_newline();
9421            self.indent_level += 1;
9422            for (i, action) in at.actions.iter().enumerate() {
9423                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9424                let is_continuation = i > 0
9425                    && matches!(
9426                        (&at.actions[i - 1], action),
9427                        (
9428                            AlterTableAction::AddColumn { .. },
9429                            AlterTableAction::AddColumn { .. }
9430                        ) | (
9431                            AlterTableAction::AddConstraint(_),
9432                            AlterTableAction::AddConstraint(_)
9433                        )
9434                    );
9435                if i > 0 {
9436                    self.write(",");
9437                    self.write_newline();
9438                }
9439                self.write_indent();
9440                self.generate_alter_action_with_continuation(action, is_continuation)?;
9441            }
9442            self.indent_level -= 1;
9443        } else {
9444            for (i, action) in at.actions.iter().enumerate() {
9445                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9446                let is_continuation = i > 0
9447                    && matches!(
9448                        (&at.actions[i - 1], action),
9449                        (
9450                            AlterTableAction::AddColumn { .. },
9451                            AlterTableAction::AddColumn { .. }
9452                        ) | (
9453                            AlterTableAction::AddConstraint(_),
9454                            AlterTableAction::AddConstraint(_)
9455                        )
9456                    );
9457                if i > 0 {
9458                    self.write(",");
9459                }
9460                self.write_space();
9461                self.generate_alter_action_with_continuation(action, is_continuation)?;
9462            }
9463        }
9464
9465        // MySQL ALTER TABLE trailing options
9466        if let Some(ref algorithm) = at.algorithm {
9467            self.write(", ");
9468            self.write_keyword("ALGORITHM");
9469            self.write("=");
9470            self.write_keyword(algorithm);
9471        }
9472        if let Some(ref lock) = at.lock {
9473            self.write(", ");
9474            self.write_keyword("LOCK");
9475            self.write("=");
9476            self.write_keyword(lock);
9477        }
9478
9479        // Restore Athena Hive context
9480        self.athena_hive_context = saved_athena_hive_context;
9481
9482        Ok(())
9483    }
9484
9485    fn generate_alter_action_with_continuation(
9486        &mut self,
9487        action: &AlterTableAction,
9488        is_continuation: bool,
9489    ) -> Result<()> {
9490        match action {
9491            AlterTableAction::AddColumn {
9492                column,
9493                if_not_exists,
9494                position,
9495            } => {
9496                use crate::dialects::DialectType;
9497                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9498                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9499                // For other dialects, repeat ADD COLUMN for each
9500                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9501                let is_tsql_like = matches!(
9502                    self.config.dialect,
9503                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9504                );
9505                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9506                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9507
9508                if is_continuation && (is_snowflake || is_tsql_like) {
9509                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9510                } else if is_snowflake {
9511                    self.write_keyword("ADD");
9512                    self.write_space();
9513                } else if is_athena {
9514                    // Athena uses ADD COLUMNS (col_def) syntax
9515                    self.write_keyword("ADD COLUMNS");
9516                    self.write(" (");
9517                } else if self.config.alter_table_include_column_keyword {
9518                    self.write_keyword("ADD COLUMN");
9519                    self.write_space();
9520                } else {
9521                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9522                    self.write_keyword("ADD");
9523                    self.write_space();
9524                }
9525
9526                if *if_not_exists {
9527                    self.write_keyword("IF NOT EXISTS");
9528                    self.write_space();
9529                }
9530                self.generate_column_def(column)?;
9531
9532                // Close parenthesis for Athena
9533                if is_athena {
9534                    self.write(")");
9535                }
9536
9537                // Column position (FIRST or AFTER)
9538                if let Some(pos) = position {
9539                    self.write_space();
9540                    match pos {
9541                        ColumnPosition::First => self.write_keyword("FIRST"),
9542                        ColumnPosition::After(col_name) => {
9543                            self.write_keyword("AFTER");
9544                            self.write_space();
9545                            self.generate_identifier(col_name)?;
9546                        }
9547                    }
9548                }
9549            }
9550            AlterTableAction::DropColumn {
9551                name,
9552                if_exists,
9553                cascade,
9554            } => {
9555                self.write_keyword("DROP COLUMN");
9556                if *if_exists {
9557                    self.write_space();
9558                    self.write_keyword("IF EXISTS");
9559                }
9560                self.write_space();
9561                self.generate_identifier(name)?;
9562                if *cascade {
9563                    self.write_space();
9564                    self.write_keyword("CASCADE");
9565                }
9566            }
9567            AlterTableAction::DropColumns { names } => {
9568                self.write_keyword("DROP COLUMNS");
9569                self.write(" (");
9570                for (i, name) in names.iter().enumerate() {
9571                    if i > 0 {
9572                        self.write(", ");
9573                    }
9574                    self.generate_identifier(name)?;
9575                }
9576                self.write(")");
9577            }
9578            AlterTableAction::RenameColumn {
9579                old_name,
9580                new_name,
9581                if_exists,
9582            } => {
9583                self.write_keyword("RENAME COLUMN");
9584                if *if_exists {
9585                    self.write_space();
9586                    self.write_keyword("IF EXISTS");
9587                }
9588                self.write_space();
9589                self.generate_identifier(old_name)?;
9590                self.write_space();
9591                self.write_keyword("TO");
9592                self.write_space();
9593                self.generate_identifier(new_name)?;
9594            }
9595            AlterTableAction::AlterColumn {
9596                name,
9597                action,
9598                use_modify_keyword,
9599            } => {
9600                use crate::dialects::DialectType;
9601                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9602                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9603                let use_modify = *use_modify_keyword
9604                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9605                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9606                if use_modify {
9607                    self.write_keyword("MODIFY COLUMN");
9608                    self.write_space();
9609                    self.generate_identifier(name)?;
9610                    // For MODIFY COLUMN, output the type directly
9611                    if let AlterColumnAction::SetDataType {
9612                        data_type,
9613                        using: _,
9614                        collate,
9615                    } = action
9616                    {
9617                        self.write_space();
9618                        self.generate_data_type(data_type)?;
9619                        // Output COLLATE clause if present
9620                        if let Some(collate_name) = collate {
9621                            self.write_space();
9622                            self.write_keyword("COLLATE");
9623                            self.write_space();
9624                            // Output as single-quoted string
9625                            self.write(&format!("'{}'", collate_name));
9626                        }
9627                    } else {
9628                        self.write_space();
9629                        self.generate_alter_column_action(action)?;
9630                    }
9631                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9632                    && matches!(action, AlterColumnAction::SetDataType { .. })
9633                {
9634                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9635                    self.write_keyword("CHANGE COLUMN");
9636                    self.write_space();
9637                    self.generate_identifier(name)?;
9638                    self.write_space();
9639                    self.generate_identifier(name)?;
9640                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9641                        self.write_space();
9642                        self.generate_data_type(data_type)?;
9643                    }
9644                } else {
9645                    self.write_keyword("ALTER COLUMN");
9646                    self.write_space();
9647                    self.generate_identifier(name)?;
9648                    self.write_space();
9649                    self.generate_alter_column_action(action)?;
9650                }
9651            }
9652            AlterTableAction::RenameTable(new_name) => {
9653                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9654                let mysql_like = matches!(
9655                    self.config.dialect,
9656                    Some(DialectType::MySQL)
9657                        | Some(DialectType::Doris)
9658                        | Some(DialectType::StarRocks)
9659                        | Some(DialectType::SingleStore)
9660                );
9661                if mysql_like {
9662                    self.write_keyword("RENAME");
9663                } else {
9664                    self.write_keyword("RENAME TO");
9665                }
9666                self.write_space();
9667                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9668                let rename_table_with_db = !matches!(
9669                    self.config.dialect,
9670                    Some(DialectType::Doris)
9671                        | Some(DialectType::DuckDB)
9672                        | Some(DialectType::BigQuery)
9673                        | Some(DialectType::PostgreSQL)
9674                );
9675                if !rename_table_with_db {
9676                    let mut stripped = new_name.clone();
9677                    stripped.schema = None;
9678                    stripped.catalog = None;
9679                    self.generate_table(&stripped)?;
9680                } else {
9681                    self.generate_table(new_name)?;
9682                }
9683            }
9684            AlterTableAction::AddConstraint(constraint) => {
9685                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9686                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9687                if !is_continuation {
9688                    self.write_keyword("ADD");
9689                    self.write_space();
9690                }
9691                self.generate_table_constraint(constraint)?;
9692            }
9693            AlterTableAction::DropConstraint { name, if_exists } => {
9694                self.write_keyword("DROP CONSTRAINT");
9695                if *if_exists {
9696                    self.write_space();
9697                    self.write_keyword("IF EXISTS");
9698                }
9699                self.write_space();
9700                self.generate_identifier(name)?;
9701            }
9702            AlterTableAction::DropForeignKey { name } => {
9703                self.write_keyword("DROP FOREIGN KEY");
9704                self.write_space();
9705                self.generate_identifier(name)?;
9706            }
9707            AlterTableAction::DropPartition {
9708                partitions,
9709                if_exists,
9710            } => {
9711                self.write_keyword("DROP");
9712                if *if_exists {
9713                    self.write_space();
9714                    self.write_keyword("IF EXISTS");
9715                }
9716                for (i, partition) in partitions.iter().enumerate() {
9717                    if i > 0 {
9718                        self.write(",");
9719                    }
9720                    self.write_space();
9721                    self.write_keyword("PARTITION");
9722                    // Check for special ClickHouse partition formats
9723                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9724                        // ClickHouse: PARTITION <expression>
9725                        self.write_space();
9726                        self.generate_expression(&partition[0].1)?;
9727                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9728                        // ClickHouse: PARTITION ALL
9729                        self.write_space();
9730                        self.write_keyword("ALL");
9731                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9732                        // ClickHouse: PARTITION ID 'string'
9733                        self.write_space();
9734                        self.write_keyword("ID");
9735                        self.write_space();
9736                        self.generate_expression(&partition[0].1)?;
9737                    } else {
9738                        // Standard SQL: PARTITION(key=value, ...)
9739                        self.write("(");
9740                        for (j, (key, value)) in partition.iter().enumerate() {
9741                            if j > 0 {
9742                                self.write(", ");
9743                            }
9744                            self.generate_identifier(key)?;
9745                            self.write(" = ");
9746                            self.generate_expression(value)?;
9747                        }
9748                        self.write(")");
9749                    }
9750                }
9751            }
9752            AlterTableAction::Delete { where_clause } => {
9753                self.write_keyword("DELETE");
9754                self.write_space();
9755                self.write_keyword("WHERE");
9756                self.write_space();
9757                self.generate_expression(where_clause)?;
9758            }
9759            AlterTableAction::SwapWith(target) => {
9760                self.write_keyword("SWAP WITH");
9761                self.write_space();
9762                self.generate_table(target)?;
9763            }
9764            AlterTableAction::SetProperty { properties } => {
9765                use crate::dialects::DialectType;
9766                self.write_keyword("SET");
9767                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9768                let is_trino_presto = matches!(
9769                    self.config.dialect,
9770                    Some(DialectType::Trino) | Some(DialectType::Presto)
9771                );
9772                if is_trino_presto {
9773                    self.write_space();
9774                    self.write_keyword("PROPERTIES");
9775                }
9776                let eq = if is_trino_presto { " = " } else { "=" };
9777                for (i, (key, value)) in properties.iter().enumerate() {
9778                    if i > 0 {
9779                        self.write(",");
9780                    }
9781                    self.write_space();
9782                    // Handle quoted property names for Trino
9783                    if key.contains(' ') {
9784                        self.generate_string_literal(key)?;
9785                    } else {
9786                        self.write(key);
9787                    }
9788                    self.write(eq);
9789                    self.generate_expression(value)?;
9790                }
9791            }
9792            AlterTableAction::UnsetProperty { properties } => {
9793                self.write_keyword("UNSET");
9794                for (i, name) in properties.iter().enumerate() {
9795                    if i > 0 {
9796                        self.write(",");
9797                    }
9798                    self.write_space();
9799                    self.write(name);
9800                }
9801            }
9802            AlterTableAction::ClusterBy { expressions } => {
9803                self.write_keyword("CLUSTER BY");
9804                self.write(" (");
9805                for (i, expr) in expressions.iter().enumerate() {
9806                    if i > 0 {
9807                        self.write(", ");
9808                    }
9809                    self.generate_expression(expr)?;
9810                }
9811                self.write(")");
9812            }
9813            AlterTableAction::SetTag { expressions } => {
9814                self.write_keyword("SET TAG");
9815                for (i, (key, value)) in expressions.iter().enumerate() {
9816                    if i > 0 {
9817                        self.write(",");
9818                    }
9819                    self.write_space();
9820                    self.write(key);
9821                    self.write(" = ");
9822                    self.generate_expression(value)?;
9823                }
9824            }
9825            AlterTableAction::UnsetTag { names } => {
9826                self.write_keyword("UNSET TAG");
9827                for (i, name) in names.iter().enumerate() {
9828                    if i > 0 {
9829                        self.write(",");
9830                    }
9831                    self.write_space();
9832                    self.write(name);
9833                }
9834            }
9835            AlterTableAction::SetOptions { expressions } => {
9836                self.write_keyword("SET");
9837                self.write(" (");
9838                for (i, expr) in expressions.iter().enumerate() {
9839                    if i > 0 {
9840                        self.write(", ");
9841                    }
9842                    self.generate_expression(expr)?;
9843                }
9844                self.write(")");
9845            }
9846            AlterTableAction::AlterIndex { name, visible } => {
9847                self.write_keyword("ALTER INDEX");
9848                self.write_space();
9849                self.generate_identifier(name)?;
9850                self.write_space();
9851                if *visible {
9852                    self.write_keyword("VISIBLE");
9853                } else {
9854                    self.write_keyword("INVISIBLE");
9855                }
9856            }
9857            AlterTableAction::SetAttribute { attribute } => {
9858                self.write_keyword("SET");
9859                self.write_space();
9860                self.write_keyword(attribute);
9861            }
9862            AlterTableAction::SetStageFileFormat { options } => {
9863                self.write_keyword("SET");
9864                self.write_space();
9865                self.write_keyword("STAGE_FILE_FORMAT");
9866                self.write(" = (");
9867                if let Some(opts) = options {
9868                    self.generate_space_separated_properties(opts)?;
9869                }
9870                self.write(")");
9871            }
9872            AlterTableAction::SetStageCopyOptions { options } => {
9873                self.write_keyword("SET");
9874                self.write_space();
9875                self.write_keyword("STAGE_COPY_OPTIONS");
9876                self.write(" = (");
9877                if let Some(opts) = options {
9878                    self.generate_space_separated_properties(opts)?;
9879                }
9880                self.write(")");
9881            }
9882            AlterTableAction::AddColumns { columns, cascade } => {
9883                // Oracle uses ADD (...) without COLUMNS keyword
9884                // Hive/Spark uses ADD COLUMNS (...)
9885                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9886                if is_oracle {
9887                    self.write_keyword("ADD");
9888                } else {
9889                    self.write_keyword("ADD COLUMNS");
9890                }
9891                self.write(" (");
9892                for (i, col) in columns.iter().enumerate() {
9893                    if i > 0 {
9894                        self.write(", ");
9895                    }
9896                    self.generate_column_def(col)?;
9897                }
9898                self.write(")");
9899                if *cascade {
9900                    self.write_space();
9901                    self.write_keyword("CASCADE");
9902                }
9903            }
9904            AlterTableAction::ChangeColumn {
9905                old_name,
9906                new_name,
9907                data_type,
9908                comment,
9909                cascade,
9910            } => {
9911                use crate::dialects::DialectType;
9912                let is_spark = matches!(
9913                    self.config.dialect,
9914                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9915                );
9916                let is_rename = old_name.name != new_name.name;
9917
9918                if is_spark {
9919                    if is_rename {
9920                        // Spark: RENAME COLUMN old TO new
9921                        self.write_keyword("RENAME COLUMN");
9922                        self.write_space();
9923                        self.generate_identifier(old_name)?;
9924                        self.write_space();
9925                        self.write_keyword("TO");
9926                        self.write_space();
9927                        self.generate_identifier(new_name)?;
9928                    } else if comment.is_some() {
9929                        // Spark: ALTER COLUMN old COMMENT 'comment'
9930                        self.write_keyword("ALTER COLUMN");
9931                        self.write_space();
9932                        self.generate_identifier(old_name)?;
9933                        self.write_space();
9934                        self.write_keyword("COMMENT");
9935                        self.write_space();
9936                        self.write("'");
9937                        self.write(comment.as_ref().unwrap());
9938                        self.write("'");
9939                    } else if data_type.is_some() {
9940                        // Spark: ALTER COLUMN old TYPE data_type
9941                        self.write_keyword("ALTER COLUMN");
9942                        self.write_space();
9943                        self.generate_identifier(old_name)?;
9944                        self.write_space();
9945                        self.write_keyword("TYPE");
9946                        self.write_space();
9947                        self.generate_data_type(data_type.as_ref().unwrap())?;
9948                    } else {
9949                        // Fallback to CHANGE COLUMN
9950                        self.write_keyword("CHANGE COLUMN");
9951                        self.write_space();
9952                        self.generate_identifier(old_name)?;
9953                        self.write_space();
9954                        self.generate_identifier(new_name)?;
9955                    }
9956                } else {
9957                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9958                    if data_type.is_some() {
9959                        self.write_keyword("CHANGE COLUMN");
9960                    } else {
9961                        self.write_keyword("CHANGE");
9962                    }
9963                    self.write_space();
9964                    self.generate_identifier(old_name)?;
9965                    self.write_space();
9966                    self.generate_identifier(new_name)?;
9967                    if let Some(ref dt) = data_type {
9968                        self.write_space();
9969                        self.generate_data_type(dt)?;
9970                    }
9971                    if let Some(ref c) = comment {
9972                        self.write_space();
9973                        self.write_keyword("COMMENT");
9974                        self.write_space();
9975                        self.write("'");
9976                        self.write(c);
9977                        self.write("'");
9978                    }
9979                    if *cascade {
9980                        self.write_space();
9981                        self.write_keyword("CASCADE");
9982                    }
9983                }
9984            }
9985            AlterTableAction::AddPartition {
9986                partition,
9987                if_not_exists,
9988                location,
9989            } => {
9990                self.write_keyword("ADD");
9991                self.write_space();
9992                if *if_not_exists {
9993                    self.write_keyword("IF NOT EXISTS");
9994                    self.write_space();
9995                }
9996                self.generate_expression(partition)?;
9997                if let Some(ref loc) = location {
9998                    self.write_space();
9999                    self.write_keyword("LOCATION");
10000                    self.write_space();
10001                    self.generate_expression(loc)?;
10002                }
10003            }
10004            AlterTableAction::AlterSortKey {
10005                this,
10006                expressions,
10007                compound,
10008            } => {
10009                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10010                self.write_keyword("ALTER");
10011                if *compound {
10012                    self.write_space();
10013                    self.write_keyword("COMPOUND");
10014                }
10015                self.write_space();
10016                self.write_keyword("SORTKEY");
10017                self.write_space();
10018                if let Some(style) = this {
10019                    self.write_keyword(style);
10020                } else if !expressions.is_empty() {
10021                    self.write("(");
10022                    for (i, expr) in expressions.iter().enumerate() {
10023                        if i > 0 {
10024                            self.write(", ");
10025                        }
10026                        self.generate_expression(expr)?;
10027                    }
10028                    self.write(")");
10029                }
10030            }
10031            AlterTableAction::AlterDistStyle { style, distkey } => {
10032                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10033                self.write_keyword("ALTER");
10034                self.write_space();
10035                self.write_keyword("DISTSTYLE");
10036                self.write_space();
10037                self.write_keyword(style);
10038                if let Some(col) = distkey {
10039                    self.write_space();
10040                    self.write_keyword("DISTKEY");
10041                    self.write_space();
10042                    self.generate_identifier(col)?;
10043                }
10044            }
10045            AlterTableAction::SetTableProperties { properties } => {
10046                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10047                self.write_keyword("SET TABLE PROPERTIES");
10048                self.write(" (");
10049                for (i, (key, value)) in properties.iter().enumerate() {
10050                    if i > 0 {
10051                        self.write(", ");
10052                    }
10053                    self.generate_expression(key)?;
10054                    self.write(" = ");
10055                    self.generate_expression(value)?;
10056                }
10057                self.write(")");
10058            }
10059            AlterTableAction::SetLocation { location } => {
10060                // Redshift: SET LOCATION 's3://bucket/folder/'
10061                self.write_keyword("SET LOCATION");
10062                self.write_space();
10063                self.write("'");
10064                self.write(location);
10065                self.write("'");
10066            }
10067            AlterTableAction::SetFileFormat { format } => {
10068                // Redshift: SET FILE FORMAT AVRO
10069                self.write_keyword("SET FILE FORMAT");
10070                self.write_space();
10071                self.write_keyword(format);
10072            }
10073            AlterTableAction::ReplacePartition { partition, source } => {
10074                // ClickHouse: REPLACE PARTITION expr FROM source
10075                self.write_keyword("REPLACE PARTITION");
10076                self.write_space();
10077                self.generate_expression(partition)?;
10078                if let Some(src) = source {
10079                    self.write_space();
10080                    self.write_keyword("FROM");
10081                    self.write_space();
10082                    self.generate_expression(src)?;
10083                }
10084            }
10085            AlterTableAction::Raw { sql } => {
10086                self.write(sql);
10087            }
10088        }
10089        Ok(())
10090    }
10091
10092    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10093        match action {
10094            AlterColumnAction::SetDataType {
10095                data_type,
10096                using,
10097                collate,
10098            } => {
10099                use crate::dialects::DialectType;
10100                // Dialect-specific type change syntax:
10101                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10102                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10103                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10104                let is_no_prefix = matches!(
10105                    self.config.dialect,
10106                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10107                );
10108                let is_type_only = matches!(
10109                    self.config.dialect,
10110                    Some(DialectType::Redshift)
10111                        | Some(DialectType::Spark)
10112                        | Some(DialectType::Databricks)
10113                );
10114                if is_type_only {
10115                    self.write_keyword("TYPE");
10116                    self.write_space();
10117                } else if !is_no_prefix {
10118                    self.write_keyword("SET DATA TYPE");
10119                    self.write_space();
10120                }
10121                self.generate_data_type(data_type)?;
10122                if let Some(ref collation) = collate {
10123                    self.write_space();
10124                    self.write_keyword("COLLATE");
10125                    self.write_space();
10126                    self.write(collation);
10127                }
10128                if let Some(ref using_expr) = using {
10129                    self.write_space();
10130                    self.write_keyword("USING");
10131                    self.write_space();
10132                    self.generate_expression(using_expr)?;
10133                }
10134            }
10135            AlterColumnAction::SetDefault(expr) => {
10136                self.write_keyword("SET DEFAULT");
10137                self.write_space();
10138                self.generate_expression(expr)?;
10139            }
10140            AlterColumnAction::DropDefault => {
10141                self.write_keyword("DROP DEFAULT");
10142            }
10143            AlterColumnAction::SetNotNull => {
10144                self.write_keyword("SET NOT NULL");
10145            }
10146            AlterColumnAction::DropNotNull => {
10147                self.write_keyword("DROP NOT NULL");
10148            }
10149            AlterColumnAction::Comment(comment) => {
10150                self.write_keyword("COMMENT");
10151                self.write_space();
10152                self.generate_string_literal(comment)?;
10153            }
10154            AlterColumnAction::SetVisible => {
10155                self.write_keyword("SET VISIBLE");
10156            }
10157            AlterColumnAction::SetInvisible => {
10158                self.write_keyword("SET INVISIBLE");
10159            }
10160        }
10161        Ok(())
10162    }
10163
10164    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10165        self.write_keyword("CREATE");
10166
10167        if ci.unique {
10168            self.write_space();
10169            self.write_keyword("UNIQUE");
10170        }
10171
10172        // TSQL CLUSTERED/NONCLUSTERED modifier
10173        if let Some(ref clustered) = ci.clustered {
10174            self.write_space();
10175            self.write_keyword(clustered);
10176        }
10177
10178        self.write_space();
10179        self.write_keyword("INDEX");
10180
10181        // PostgreSQL CONCURRENTLY modifier
10182        if ci.concurrently {
10183            self.write_space();
10184            self.write_keyword("CONCURRENTLY");
10185        }
10186
10187        if ci.if_not_exists {
10188            self.write_space();
10189            self.write_keyword("IF NOT EXISTS");
10190        }
10191
10192        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10193        if !ci.name.name.is_empty() {
10194            self.write_space();
10195            self.generate_identifier(&ci.name)?;
10196        }
10197        self.write_space();
10198        self.write_keyword("ON");
10199        // Hive uses ON TABLE
10200        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10201            self.write_space();
10202            self.write_keyword("TABLE");
10203        }
10204        self.write_space();
10205        self.generate_table(&ci.table)?;
10206
10207        // Column list (optional for COLUMNSTORE indexes)
10208        // Standard SQL convention: ON t(a) without space before paren
10209        if !ci.columns.is_empty() || ci.using.is_some() {
10210            let space_before_paren = false;
10211
10212            if let Some(ref using) = ci.using {
10213                self.write_space();
10214                self.write_keyword("USING");
10215                self.write_space();
10216                self.write(using);
10217                if space_before_paren {
10218                    self.write(" (");
10219                } else {
10220                    self.write("(");
10221                }
10222            } else {
10223                if space_before_paren {
10224                    self.write(" (");
10225                } else {
10226                    self.write("(");
10227                }
10228            }
10229            for (i, col) in ci.columns.iter().enumerate() {
10230                if i > 0 {
10231                    self.write(", ");
10232                }
10233                self.generate_identifier(&col.column)?;
10234                if let Some(ref opclass) = col.opclass {
10235                    self.write_space();
10236                    self.write(opclass);
10237                }
10238                if col.desc {
10239                    self.write_space();
10240                    self.write_keyword("DESC");
10241                } else if col.asc {
10242                    self.write_space();
10243                    self.write_keyword("ASC");
10244                }
10245                if let Some(nulls_first) = col.nulls_first {
10246                    self.write_space();
10247                    self.write_keyword("NULLS");
10248                    self.write_space();
10249                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10250                }
10251            }
10252            self.write(")");
10253        }
10254
10255        // PostgreSQL INCLUDE (col1, col2) clause
10256        if !ci.include_columns.is_empty() {
10257            self.write_space();
10258            self.write_keyword("INCLUDE");
10259            self.write(" (");
10260            for (i, col) in ci.include_columns.iter().enumerate() {
10261                if i > 0 {
10262                    self.write(", ");
10263                }
10264                self.generate_identifier(col)?;
10265            }
10266            self.write(")");
10267        }
10268
10269        // TSQL: WITH (option=value, ...) clause
10270        if !ci.with_options.is_empty() {
10271            self.write_space();
10272            self.write_keyword("WITH");
10273            self.write(" (");
10274            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10275                if i > 0 {
10276                    self.write(", ");
10277                }
10278                self.write(key);
10279                self.write("=");
10280                self.write(value);
10281            }
10282            self.write(")");
10283        }
10284
10285        // PostgreSQL WHERE clause for partial indexes
10286        if let Some(ref where_clause) = ci.where_clause {
10287            self.write_space();
10288            self.write_keyword("WHERE");
10289            self.write_space();
10290            self.generate_expression(where_clause)?;
10291        }
10292
10293        // TSQL: ON filegroup or partition scheme clause
10294        if let Some(ref on_fg) = ci.on_filegroup {
10295            self.write_space();
10296            self.write_keyword("ON");
10297            self.write_space();
10298            self.write(on_fg);
10299        }
10300
10301        Ok(())
10302    }
10303
10304    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10305        self.write_keyword("DROP INDEX");
10306
10307        if di.concurrently {
10308            self.write_space();
10309            self.write_keyword("CONCURRENTLY");
10310        }
10311
10312        if di.if_exists {
10313            self.write_space();
10314            self.write_keyword("IF EXISTS");
10315        }
10316
10317        self.write_space();
10318        self.generate_identifier(&di.name)?;
10319
10320        if let Some(ref table) = di.table {
10321            self.write_space();
10322            self.write_keyword("ON");
10323            self.write_space();
10324            self.generate_table(table)?;
10325        }
10326
10327        Ok(())
10328    }
10329
10330    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10331        self.write_keyword("CREATE");
10332
10333        // MySQL: ALGORITHM=...
10334        if let Some(ref algorithm) = cv.algorithm {
10335            self.write_space();
10336            self.write_keyword("ALGORITHM");
10337            self.write("=");
10338            self.write_keyword(algorithm);
10339        }
10340
10341        // MySQL: DEFINER=...
10342        if let Some(ref definer) = cv.definer {
10343            self.write_space();
10344            self.write_keyword("DEFINER");
10345            self.write("=");
10346            self.write(definer);
10347        }
10348
10349        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10350        if cv.security_sql_style {
10351            if let Some(ref security) = cv.security {
10352                self.write_space();
10353                self.write_keyword("SQL SECURITY");
10354                self.write_space();
10355                match security {
10356                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10357                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10358                    FunctionSecurity::None => self.write_keyword("NONE"),
10359                }
10360            }
10361        }
10362
10363        if cv.or_replace {
10364            self.write_space();
10365            self.write_keyword("OR REPLACE");
10366        }
10367
10368        if cv.temporary {
10369            self.write_space();
10370            self.write_keyword("TEMPORARY");
10371        }
10372
10373        if cv.materialized {
10374            self.write_space();
10375            self.write_keyword("MATERIALIZED");
10376        }
10377
10378        // Snowflake: SECURE VIEW
10379        if cv.secure {
10380            self.write_space();
10381            self.write_keyword("SECURE");
10382        }
10383
10384        self.write_space();
10385        self.write_keyword("VIEW");
10386
10387        if cv.if_not_exists {
10388            self.write_space();
10389            self.write_keyword("IF NOT EXISTS");
10390        }
10391
10392        self.write_space();
10393        self.generate_table(&cv.name)?;
10394
10395        // ClickHouse: ON CLUSTER clause
10396        if let Some(ref on_cluster) = cv.on_cluster {
10397            self.write_space();
10398            self.generate_on_cluster(on_cluster)?;
10399        }
10400
10401        // ClickHouse: TO destination_table
10402        if let Some(ref to_table) = cv.to_table {
10403            self.write_space();
10404            self.write_keyword("TO");
10405            self.write_space();
10406            self.generate_table(to_table)?;
10407        }
10408
10409        // For regular VIEW: columns come before COPY GRANTS
10410        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10411        if !cv.materialized {
10412            // Regular VIEW: columns first
10413            if !cv.columns.is_empty() {
10414                self.write(" (");
10415                for (i, col) in cv.columns.iter().enumerate() {
10416                    if i > 0 {
10417                        self.write(", ");
10418                    }
10419                    self.generate_identifier(&col.name)?;
10420                    // BigQuery: OPTIONS (key=value, ...) on view column
10421                    if !col.options.is_empty() {
10422                        self.write_space();
10423                        self.generate_options_clause(&col.options)?;
10424                    }
10425                    if let Some(ref comment) = col.comment {
10426                        self.write_space();
10427                        self.write_keyword("COMMENT");
10428                        self.write_space();
10429                        self.generate_string_literal(comment)?;
10430                    }
10431                }
10432                self.write(")");
10433            }
10434
10435            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10436            if !cv.security_sql_style {
10437                if let Some(ref security) = cv.security {
10438                    self.write_space();
10439                    self.write_keyword("SECURITY");
10440                    self.write_space();
10441                    match security {
10442                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10443                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10444                        FunctionSecurity::None => self.write_keyword("NONE"),
10445                    }
10446                }
10447            }
10448
10449            // Snowflake: COPY GRANTS
10450            if cv.copy_grants {
10451                self.write_space();
10452                self.write_keyword("COPY GRANTS");
10453            }
10454        } else {
10455            // MATERIALIZED VIEW: COPY GRANTS first
10456            if cv.copy_grants {
10457                self.write_space();
10458                self.write_keyword("COPY GRANTS");
10459            }
10460
10461            // Doris: If we have a schema (typed columns), generate that instead
10462            if let Some(ref schema) = cv.schema {
10463                self.write(" (");
10464                for (i, expr) in schema.expressions.iter().enumerate() {
10465                    if i > 0 {
10466                        self.write(", ");
10467                    }
10468                    self.generate_expression(expr)?;
10469                }
10470                self.write(")");
10471            } else if !cv.columns.is_empty() {
10472                // Then columns (simple column names without types)
10473                self.write(" (");
10474                for (i, col) in cv.columns.iter().enumerate() {
10475                    if i > 0 {
10476                        self.write(", ");
10477                    }
10478                    self.generate_identifier(&col.name)?;
10479                    // BigQuery: OPTIONS (key=value, ...) on view column
10480                    if !col.options.is_empty() {
10481                        self.write_space();
10482                        self.generate_options_clause(&col.options)?;
10483                    }
10484                    if let Some(ref comment) = col.comment {
10485                        self.write_space();
10486                        self.write_keyword("COMMENT");
10487                        self.write_space();
10488                        self.generate_string_literal(comment)?;
10489                    }
10490                }
10491                self.write(")");
10492            }
10493
10494            // Doris: KEY (columns) for materialized views
10495            if let Some(ref unique_key) = cv.unique_key {
10496                self.write_space();
10497                self.write_keyword("KEY");
10498                self.write(" (");
10499                for (i, expr) in unique_key.expressions.iter().enumerate() {
10500                    if i > 0 {
10501                        self.write(", ");
10502                    }
10503                    self.generate_expression(expr)?;
10504                }
10505                self.write(")");
10506            }
10507        }
10508
10509        // Snowflake: COMMENT = 'text'
10510        if let Some(ref comment) = cv.comment {
10511            self.write_space();
10512            self.write_keyword("COMMENT");
10513            self.write("=");
10514            self.generate_string_literal(comment)?;
10515        }
10516
10517        // Snowflake: TAG (name='value', ...)
10518        if !cv.tags.is_empty() {
10519            self.write_space();
10520            self.write_keyword("TAG");
10521            self.write(" (");
10522            for (i, (name, value)) in cv.tags.iter().enumerate() {
10523                if i > 0 {
10524                    self.write(", ");
10525                }
10526                self.write(name);
10527                self.write("='");
10528                self.write(value);
10529                self.write("'");
10530            }
10531            self.write(")");
10532        }
10533
10534        // BigQuery: OPTIONS (key=value, ...)
10535        if !cv.options.is_empty() {
10536            self.write_space();
10537            self.generate_options_clause(&cv.options)?;
10538        }
10539
10540        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10541        if let Some(ref build) = cv.build {
10542            self.write_space();
10543            self.write_keyword("BUILD");
10544            self.write_space();
10545            self.write_keyword(build);
10546        }
10547
10548        // Doris: REFRESH clause for materialized views
10549        if let Some(ref refresh) = cv.refresh {
10550            self.write_space();
10551            self.generate_refresh_trigger_property(refresh)?;
10552        }
10553
10554        // Redshift: AUTO REFRESH YES|NO for materialized views
10555        if let Some(auto_refresh) = cv.auto_refresh {
10556            self.write_space();
10557            self.write_keyword("AUTO REFRESH");
10558            self.write_space();
10559            if auto_refresh {
10560                self.write_keyword("YES");
10561            } else {
10562                self.write_keyword("NO");
10563            }
10564        }
10565
10566        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10567        for prop in &cv.table_properties {
10568            self.write_space();
10569            self.generate_expression(prop)?;
10570        }
10571
10572        // Only output AS clause if there's a real query (not just NULL placeholder)
10573        if !matches!(&cv.query, Expression::Null(_)) {
10574            self.write_space();
10575            self.write_keyword("AS");
10576            self.write_space();
10577
10578            // Teradata: LOCKING clause (between AS and query)
10579            if let Some(ref mode) = cv.locking_mode {
10580                self.write_keyword("LOCKING");
10581                self.write_space();
10582                self.write_keyword(mode);
10583                if let Some(ref access) = cv.locking_access {
10584                    self.write_space();
10585                    self.write_keyword("FOR");
10586                    self.write_space();
10587                    self.write_keyword(access);
10588                }
10589                self.write_space();
10590            }
10591
10592            if cv.query_parenthesized {
10593                self.write("(");
10594            }
10595            self.generate_expression(&cv.query)?;
10596            if cv.query_parenthesized {
10597                self.write(")");
10598            }
10599        }
10600
10601        // Redshift: WITH NO SCHEMA BINDING (after query)
10602        if cv.no_schema_binding {
10603            self.write_space();
10604            self.write_keyword("WITH NO SCHEMA BINDING");
10605        }
10606
10607        Ok(())
10608    }
10609
10610    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10611        self.write_keyword("DROP");
10612
10613        if dv.materialized {
10614            self.write_space();
10615            self.write_keyword("MATERIALIZED");
10616        }
10617
10618        self.write_space();
10619        self.write_keyword("VIEW");
10620
10621        if dv.if_exists {
10622            self.write_space();
10623            self.write_keyword("IF EXISTS");
10624        }
10625
10626        self.write_space();
10627        self.generate_table(&dv.name)?;
10628
10629        Ok(())
10630    }
10631
10632    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10633        match tr.target {
10634            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10635            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10636        }
10637        if tr.if_exists {
10638            self.write_space();
10639            self.write_keyword("IF EXISTS");
10640        }
10641        self.write_space();
10642        self.generate_table(&tr.table)?;
10643
10644        // ClickHouse: ON CLUSTER clause
10645        if let Some(ref on_cluster) = tr.on_cluster {
10646            self.write_space();
10647            self.generate_on_cluster(on_cluster)?;
10648        }
10649
10650        // Check if first table has a * (multi-table with star)
10651        if !tr.extra_tables.is_empty() {
10652            // Check if the first entry matches the main table (star case)
10653            let skip_first = if let Some(first) = tr.extra_tables.first() {
10654                first.table.name == tr.table.name && first.star
10655            } else {
10656                false
10657            };
10658
10659            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10660            let strip_star = matches!(
10661                self.config.dialect,
10662                Some(crate::dialects::DialectType::PostgreSQL)
10663                    | Some(crate::dialects::DialectType::Redshift)
10664            );
10665            if skip_first && !strip_star {
10666                self.write("*");
10667            }
10668
10669            // Generate additional tables
10670            for (i, entry) in tr.extra_tables.iter().enumerate() {
10671                if i == 0 && skip_first {
10672                    continue; // Already handled the star for first table
10673                }
10674                self.write(", ");
10675                self.generate_table(&entry.table)?;
10676                if entry.star && !strip_star {
10677                    self.write("*");
10678                }
10679            }
10680        }
10681
10682        // RESTART/CONTINUE IDENTITY
10683        if let Some(identity) = &tr.identity {
10684            self.write_space();
10685            match identity {
10686                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10687                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10688            }
10689        }
10690
10691        if tr.cascade {
10692            self.write_space();
10693            self.write_keyword("CASCADE");
10694        }
10695
10696        if tr.restrict {
10697            self.write_space();
10698            self.write_keyword("RESTRICT");
10699        }
10700
10701        // Output Hive PARTITION clause
10702        if let Some(ref partition) = tr.partition {
10703            self.write_space();
10704            self.generate_expression(partition)?;
10705        }
10706
10707        Ok(())
10708    }
10709
10710    fn generate_use(&mut self, u: &Use) -> Result<()> {
10711        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10712        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10713            self.write_keyword("DATABASE");
10714            self.write_space();
10715            self.generate_identifier(&u.this)?;
10716            return Ok(());
10717        }
10718
10719        self.write_keyword("USE");
10720
10721        if let Some(kind) = &u.kind {
10722            self.write_space();
10723            match kind {
10724                UseKind::Database => self.write_keyword("DATABASE"),
10725                UseKind::Schema => self.write_keyword("SCHEMA"),
10726                UseKind::Role => self.write_keyword("ROLE"),
10727                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10728                UseKind::Catalog => self.write_keyword("CATALOG"),
10729                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10730            }
10731        }
10732
10733        self.write_space();
10734        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10735        // without quoting, since these are keywords not identifiers
10736        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10737            self.write(&u.this.name);
10738        } else {
10739            self.generate_identifier(&u.this)?;
10740        }
10741        Ok(())
10742    }
10743
10744    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10745        self.write_keyword("CACHE");
10746        if c.lazy {
10747            self.write_space();
10748            self.write_keyword("LAZY");
10749        }
10750        self.write_space();
10751        self.write_keyword("TABLE");
10752        self.write_space();
10753        self.generate_identifier(&c.table)?;
10754
10755        // OPTIONS clause
10756        if !c.options.is_empty() {
10757            self.write_space();
10758            self.write_keyword("OPTIONS");
10759            self.write("(");
10760            for (i, (key, value)) in c.options.iter().enumerate() {
10761                if i > 0 {
10762                    self.write(", ");
10763                }
10764                self.generate_expression(key)?;
10765                self.write(" = ");
10766                self.generate_expression(value)?;
10767            }
10768            self.write(")");
10769        }
10770
10771        // AS query
10772        if let Some(query) = &c.query {
10773            self.write_space();
10774            self.write_keyword("AS");
10775            self.write_space();
10776            self.generate_expression(query)?;
10777        }
10778
10779        Ok(())
10780    }
10781
10782    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10783        self.write_keyword("UNCACHE TABLE");
10784        if u.if_exists {
10785            self.write_space();
10786            self.write_keyword("IF EXISTS");
10787        }
10788        self.write_space();
10789        self.generate_identifier(&u.table)?;
10790        Ok(())
10791    }
10792
10793    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10794        self.write_keyword("LOAD DATA");
10795        if l.local {
10796            self.write_space();
10797            self.write_keyword("LOCAL");
10798        }
10799        self.write_space();
10800        self.write_keyword("INPATH");
10801        self.write_space();
10802        self.write("'");
10803        self.write(&l.inpath);
10804        self.write("'");
10805
10806        if l.overwrite {
10807            self.write_space();
10808            self.write_keyword("OVERWRITE");
10809        }
10810
10811        self.write_space();
10812        self.write_keyword("INTO TABLE");
10813        self.write_space();
10814        self.generate_expression(&l.table)?;
10815
10816        // PARTITION clause
10817        if !l.partition.is_empty() {
10818            self.write_space();
10819            self.write_keyword("PARTITION");
10820            self.write("(");
10821            for (i, (col, val)) in l.partition.iter().enumerate() {
10822                if i > 0 {
10823                    self.write(", ");
10824                }
10825                self.generate_identifier(col)?;
10826                self.write(" = ");
10827                self.generate_expression(val)?;
10828            }
10829            self.write(")");
10830        }
10831
10832        // INPUTFORMAT clause
10833        if let Some(fmt) = &l.input_format {
10834            self.write_space();
10835            self.write_keyword("INPUTFORMAT");
10836            self.write_space();
10837            self.write("'");
10838            self.write(fmt);
10839            self.write("'");
10840        }
10841
10842        // SERDE clause
10843        if let Some(serde) = &l.serde {
10844            self.write_space();
10845            self.write_keyword("SERDE");
10846            self.write_space();
10847            self.write("'");
10848            self.write(serde);
10849            self.write("'");
10850        }
10851
10852        Ok(())
10853    }
10854
10855    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10856        self.write_keyword("PRAGMA");
10857        self.write_space();
10858
10859        // Schema prefix if present
10860        if let Some(schema) = &p.schema {
10861            self.generate_identifier(schema)?;
10862            self.write(".");
10863        }
10864
10865        // Pragma name
10866        self.generate_identifier(&p.name)?;
10867
10868        // Value assignment or function call
10869        if let Some(value) = &p.value {
10870            self.write(" = ");
10871            self.generate_expression(value)?;
10872        } else if !p.args.is_empty() {
10873            self.write("(");
10874            for (i, arg) in p.args.iter().enumerate() {
10875                if i > 0 {
10876                    self.write(", ");
10877                }
10878                self.generate_expression(arg)?;
10879            }
10880            self.write(")");
10881        }
10882
10883        Ok(())
10884    }
10885
10886    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10887        self.write_keyword("GRANT");
10888        self.write_space();
10889
10890        // Privileges (with optional column lists)
10891        for (i, privilege) in g.privileges.iter().enumerate() {
10892            if i > 0 {
10893                self.write(", ");
10894            }
10895            self.write_keyword(&privilege.name);
10896            // Output column list if present: SELECT(col1, col2)
10897            if !privilege.columns.is_empty() {
10898                self.write("(");
10899                for (j, col) in privilege.columns.iter().enumerate() {
10900                    if j > 0 {
10901                        self.write(", ");
10902                    }
10903                    self.write(col);
10904                }
10905                self.write(")");
10906            }
10907        }
10908
10909        self.write_space();
10910        self.write_keyword("ON");
10911        self.write_space();
10912
10913        // Object kind (TABLE, SCHEMA, etc.)
10914        if let Some(kind) = &g.kind {
10915            self.write_keyword(kind);
10916            self.write_space();
10917        }
10918
10919        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10920        {
10921            use crate::dialects::DialectType;
10922            let should_upper = matches!(
10923                self.config.dialect,
10924                Some(DialectType::PostgreSQL)
10925                    | Some(DialectType::CockroachDB)
10926                    | Some(DialectType::Materialize)
10927                    | Some(DialectType::RisingWave)
10928            ) && (g.kind.as_deref() == Some("FUNCTION")
10929                || g.kind.as_deref() == Some("PROCEDURE"));
10930            if should_upper {
10931                use crate::expressions::Identifier;
10932                let upper_id = Identifier {
10933                    name: g.securable.name.to_uppercase(),
10934                    quoted: g.securable.quoted,
10935                    ..g.securable.clone()
10936                };
10937                self.generate_identifier(&upper_id)?;
10938            } else {
10939                self.generate_identifier(&g.securable)?;
10940            }
10941        }
10942
10943        // Function parameter types (if present)
10944        if !g.function_params.is_empty() {
10945            self.write("(");
10946            for (i, param) in g.function_params.iter().enumerate() {
10947                if i > 0 {
10948                    self.write(", ");
10949                }
10950                self.write(param);
10951            }
10952            self.write(")");
10953        }
10954
10955        self.write_space();
10956        self.write_keyword("TO");
10957        self.write_space();
10958
10959        // Principals
10960        for (i, principal) in g.principals.iter().enumerate() {
10961            if i > 0 {
10962                self.write(", ");
10963            }
10964            if principal.is_role {
10965                self.write_keyword("ROLE");
10966                self.write_space();
10967            } else if principal.is_group {
10968                self.write_keyword("GROUP");
10969                self.write_space();
10970            }
10971            self.generate_identifier(&principal.name)?;
10972        }
10973
10974        // WITH GRANT OPTION
10975        if g.grant_option {
10976            self.write_space();
10977            self.write_keyword("WITH GRANT OPTION");
10978        }
10979
10980        // TSQL: AS principal
10981        if let Some(ref principal) = g.as_principal {
10982            self.write_space();
10983            self.write_keyword("AS");
10984            self.write_space();
10985            self.generate_identifier(principal)?;
10986        }
10987
10988        Ok(())
10989    }
10990
10991    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10992        self.write_keyword("REVOKE");
10993        self.write_space();
10994
10995        // GRANT OPTION FOR
10996        if r.grant_option {
10997            self.write_keyword("GRANT OPTION FOR");
10998            self.write_space();
10999        }
11000
11001        // Privileges (with optional column lists)
11002        for (i, privilege) in r.privileges.iter().enumerate() {
11003            if i > 0 {
11004                self.write(", ");
11005            }
11006            self.write_keyword(&privilege.name);
11007            // Output column list if present: SELECT(col1, col2)
11008            if !privilege.columns.is_empty() {
11009                self.write("(");
11010                for (j, col) in privilege.columns.iter().enumerate() {
11011                    if j > 0 {
11012                        self.write(", ");
11013                    }
11014                    self.write(col);
11015                }
11016                self.write(")");
11017            }
11018        }
11019
11020        self.write_space();
11021        self.write_keyword("ON");
11022        self.write_space();
11023
11024        // Object kind
11025        if let Some(kind) = &r.kind {
11026            self.write_keyword(kind);
11027            self.write_space();
11028        }
11029
11030        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11031        {
11032            use crate::dialects::DialectType;
11033            let should_upper = matches!(
11034                self.config.dialect,
11035                Some(DialectType::PostgreSQL)
11036                    | Some(DialectType::CockroachDB)
11037                    | Some(DialectType::Materialize)
11038                    | Some(DialectType::RisingWave)
11039            ) && (r.kind.as_deref() == Some("FUNCTION")
11040                || r.kind.as_deref() == Some("PROCEDURE"));
11041            if should_upper {
11042                use crate::expressions::Identifier;
11043                let upper_id = Identifier {
11044                    name: r.securable.name.to_uppercase(),
11045                    quoted: r.securable.quoted,
11046                    ..r.securable.clone()
11047                };
11048                self.generate_identifier(&upper_id)?;
11049            } else {
11050                self.generate_identifier(&r.securable)?;
11051            }
11052        }
11053
11054        // Function parameter types (if present)
11055        if !r.function_params.is_empty() {
11056            self.write("(");
11057            for (i, param) in r.function_params.iter().enumerate() {
11058                if i > 0 {
11059                    self.write(", ");
11060                }
11061                self.write(param);
11062            }
11063            self.write(")");
11064        }
11065
11066        self.write_space();
11067        self.write_keyword("FROM");
11068        self.write_space();
11069
11070        // Principals
11071        for (i, principal) in r.principals.iter().enumerate() {
11072            if i > 0 {
11073                self.write(", ");
11074            }
11075            if principal.is_role {
11076                self.write_keyword("ROLE");
11077                self.write_space();
11078            } else if principal.is_group {
11079                self.write_keyword("GROUP");
11080                self.write_space();
11081            }
11082            self.generate_identifier(&principal.name)?;
11083        }
11084
11085        // CASCADE or RESTRICT
11086        if r.cascade {
11087            self.write_space();
11088            self.write_keyword("CASCADE");
11089        } else if r.restrict {
11090            self.write_space();
11091            self.write_keyword("RESTRICT");
11092        }
11093
11094        Ok(())
11095    }
11096
11097    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11098        self.write_keyword("COMMENT");
11099
11100        // IF EXISTS
11101        if c.exists {
11102            self.write_space();
11103            self.write_keyword("IF EXISTS");
11104        }
11105
11106        self.write_space();
11107        self.write_keyword("ON");
11108
11109        // MATERIALIZED
11110        if c.materialized {
11111            self.write_space();
11112            self.write_keyword("MATERIALIZED");
11113        }
11114
11115        self.write_space();
11116        self.write_keyword(&c.kind);
11117        self.write_space();
11118
11119        // Object name
11120        self.generate_expression(&c.this)?;
11121
11122        self.write_space();
11123        self.write_keyword("IS");
11124        self.write_space();
11125
11126        // Comment expression
11127        self.generate_expression(&c.expression)?;
11128
11129        Ok(())
11130    }
11131
11132    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11133        self.write_keyword("SET");
11134
11135        for (i, item) in s.items.iter().enumerate() {
11136            if i > 0 {
11137                self.write(",");
11138            }
11139            self.write_space();
11140
11141            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11142            if let Some(ref kind) = item.kind {
11143                self.write_keyword(kind);
11144                self.write_space();
11145            }
11146
11147            // Check for special SET forms by name
11148            let name_str = match &item.name {
11149                Expression::Identifier(id) => Some(id.name.as_str()),
11150                _ => None,
11151            };
11152
11153            let is_transaction = name_str == Some("TRANSACTION");
11154            let is_character_set = name_str == Some("CHARACTER SET");
11155            let is_names = name_str == Some("NAMES");
11156            let is_collate = name_str == Some("COLLATE");
11157            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11158            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11159            let is_variable = has_variable_kind || name_has_variable_prefix;
11160            let is_value_only =
11161                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11162
11163            if is_transaction {
11164                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11165                self.write_keyword("TRANSACTION");
11166                if let Expression::Identifier(id) = &item.value {
11167                    if !id.name.is_empty() {
11168                        self.write_space();
11169                        self.write(&id.name);
11170                    }
11171                }
11172            } else if is_character_set {
11173                // Output: SET CHARACTER SET <charset>
11174                self.write_keyword("CHARACTER SET");
11175                self.write_space();
11176                self.generate_set_value(&item.value)?;
11177            } else if is_names {
11178                // Output: SET NAMES <charset>
11179                self.write_keyword("NAMES");
11180                self.write_space();
11181                self.generate_set_value(&item.value)?;
11182            } else if is_collate {
11183                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11184                self.write_keyword("COLLATE");
11185                self.write_space();
11186                self.generate_set_value(&item.value)?;
11187            } else if is_variable {
11188                // Output: SET [VARIABLE] <name> = <value>
11189                // If kind=VARIABLE, the keyword was already written above.
11190                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11191                if name_has_variable_prefix && !has_variable_kind {
11192                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11193                        self.write_keyword("VARIABLE");
11194                        self.write_space();
11195                    }
11196                }
11197                // Extract actual variable name (strip VARIABLE prefix if present)
11198                if let Some(ns) = name_str {
11199                    let var_name = if name_has_variable_prefix {
11200                        &ns["VARIABLE ".len()..]
11201                    } else {
11202                        ns
11203                    };
11204                    self.write(var_name);
11205                } else {
11206                    self.generate_expression(&item.name)?;
11207                }
11208                self.write(" = ");
11209                self.generate_set_value(&item.value)?;
11210            } else if is_value_only {
11211                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11212                self.generate_expression(&item.name)?;
11213            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11214                // SET key value without = (TSQL style)
11215                self.generate_expression(&item.name)?;
11216                self.write_space();
11217                self.generate_set_value(&item.value)?;
11218            } else {
11219                // Standard: variable = value
11220                // SET item names should not be quoted (they are config parameter names, not column refs)
11221                match &item.name {
11222                    Expression::Identifier(id) => {
11223                        self.write(&id.name);
11224                    }
11225                    _ => {
11226                        self.generate_expression(&item.name)?;
11227                    }
11228                }
11229                self.write(" = ");
11230                self.generate_set_value(&item.value)?;
11231            }
11232        }
11233
11234        Ok(())
11235    }
11236
11237    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11238    /// directly to avoid reserved keyword quoting.
11239    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11240        if let Expression::Identifier(id) = value {
11241            match id.name.as_str() {
11242                "DEFAULT" | "ON" | "OFF" => {
11243                    self.write_keyword(&id.name);
11244                    return Ok(());
11245                }
11246                _ => {}
11247            }
11248        }
11249        self.generate_expression(value)
11250    }
11251
11252    // ==================== Phase 4: Additional DDL Generation ====================
11253
11254    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11255        self.write_keyword("ALTER");
11256        // MySQL modifiers before VIEW
11257        if let Some(ref algorithm) = av.algorithm {
11258            self.write_space();
11259            self.write_keyword("ALGORITHM");
11260            self.write(" = ");
11261            self.write_keyword(algorithm);
11262        }
11263        if let Some(ref definer) = av.definer {
11264            self.write_space();
11265            self.write_keyword("DEFINER");
11266            self.write(" = ");
11267            self.write(definer);
11268        }
11269        if let Some(ref sql_security) = av.sql_security {
11270            self.write_space();
11271            self.write_keyword("SQL SECURITY");
11272            self.write(" = ");
11273            self.write_keyword(sql_security);
11274        }
11275        self.write_space();
11276        self.write_keyword("VIEW");
11277        self.write_space();
11278        self.generate_table(&av.name)?;
11279
11280        // Hive: Column aliases with optional COMMENT
11281        if !av.columns.is_empty() {
11282            self.write(" (");
11283            for (i, col) in av.columns.iter().enumerate() {
11284                if i > 0 {
11285                    self.write(", ");
11286                }
11287                self.generate_identifier(&col.name)?;
11288                if let Some(ref comment) = col.comment {
11289                    self.write_space();
11290                    self.write_keyword("COMMENT");
11291                    self.write(" ");
11292                    self.generate_string_literal(comment)?;
11293                }
11294            }
11295            self.write(")");
11296        }
11297
11298        // TSQL: WITH option before actions
11299        if let Some(ref opt) = av.with_option {
11300            self.write_space();
11301            self.write_keyword("WITH");
11302            self.write_space();
11303            self.write_keyword(opt);
11304        }
11305
11306        for action in &av.actions {
11307            self.write_space();
11308            match action {
11309                AlterViewAction::Rename(new_name) => {
11310                    self.write_keyword("RENAME TO");
11311                    self.write_space();
11312                    self.generate_table(new_name)?;
11313                }
11314                AlterViewAction::OwnerTo(owner) => {
11315                    self.write_keyword("OWNER TO");
11316                    self.write_space();
11317                    self.generate_identifier(owner)?;
11318                }
11319                AlterViewAction::SetSchema(schema) => {
11320                    self.write_keyword("SET SCHEMA");
11321                    self.write_space();
11322                    self.generate_identifier(schema)?;
11323                }
11324                AlterViewAction::SetAuthorization(auth) => {
11325                    self.write_keyword("SET AUTHORIZATION");
11326                    self.write_space();
11327                    self.write(auth);
11328                }
11329                AlterViewAction::AlterColumn { name, action } => {
11330                    self.write_keyword("ALTER COLUMN");
11331                    self.write_space();
11332                    self.generate_identifier(name)?;
11333                    self.write_space();
11334                    self.generate_alter_column_action(action)?;
11335                }
11336                AlterViewAction::AsSelect(query) => {
11337                    self.write_keyword("AS");
11338                    self.write_space();
11339                    self.generate_expression(query)?;
11340                }
11341                AlterViewAction::SetTblproperties(props) => {
11342                    self.write_keyword("SET TBLPROPERTIES");
11343                    self.write(" (");
11344                    for (i, (key, value)) in props.iter().enumerate() {
11345                        if i > 0 {
11346                            self.write(", ");
11347                        }
11348                        self.generate_string_literal(key)?;
11349                        self.write("=");
11350                        self.generate_string_literal(value)?;
11351                    }
11352                    self.write(")");
11353                }
11354                AlterViewAction::UnsetTblproperties(keys) => {
11355                    self.write_keyword("UNSET TBLPROPERTIES");
11356                    self.write(" (");
11357                    for (i, key) in keys.iter().enumerate() {
11358                        if i > 0 {
11359                            self.write(", ");
11360                        }
11361                        self.generate_string_literal(key)?;
11362                    }
11363                    self.write(")");
11364                }
11365            }
11366        }
11367
11368        Ok(())
11369    }
11370
11371    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11372        self.write_keyword("ALTER INDEX");
11373        self.write_space();
11374        self.generate_identifier(&ai.name)?;
11375
11376        if let Some(table) = &ai.table {
11377            self.write_space();
11378            self.write_keyword("ON");
11379            self.write_space();
11380            self.generate_table(table)?;
11381        }
11382
11383        for action in &ai.actions {
11384            self.write_space();
11385            match action {
11386                AlterIndexAction::Rename(new_name) => {
11387                    self.write_keyword("RENAME TO");
11388                    self.write_space();
11389                    self.generate_identifier(new_name)?;
11390                }
11391                AlterIndexAction::SetTablespace(tablespace) => {
11392                    self.write_keyword("SET TABLESPACE");
11393                    self.write_space();
11394                    self.generate_identifier(tablespace)?;
11395                }
11396                AlterIndexAction::Visible(visible) => {
11397                    if *visible {
11398                        self.write_keyword("VISIBLE");
11399                    } else {
11400                        self.write_keyword("INVISIBLE");
11401                    }
11402                }
11403            }
11404        }
11405
11406        Ok(())
11407    }
11408
11409    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11410        // Output leading comments
11411        for comment in &cs.leading_comments {
11412            self.write_formatted_comment(comment);
11413            self.write_space();
11414        }
11415
11416        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11417        let saved_athena_hive_context = self.athena_hive_context;
11418        if matches!(
11419            self.config.dialect,
11420            Some(crate::dialects::DialectType::Athena)
11421        ) {
11422            self.athena_hive_context = true;
11423        }
11424
11425        self.write_keyword("CREATE SCHEMA");
11426
11427        if cs.if_not_exists {
11428            self.write_space();
11429            self.write_keyword("IF NOT EXISTS");
11430        }
11431
11432        self.write_space();
11433        self.generate_identifier(&cs.name)?;
11434
11435        if let Some(ref clone_src) = cs.clone_from {
11436            self.write_keyword(" CLONE ");
11437            self.generate_identifier(clone_src)?;
11438        }
11439
11440        if let Some(ref at_clause) = cs.at_clause {
11441            self.write_space();
11442            self.generate_expression(at_clause)?;
11443        }
11444
11445        if let Some(auth) = &cs.authorization {
11446            self.write_space();
11447            self.write_keyword("AUTHORIZATION");
11448            self.write_space();
11449            self.generate_identifier(auth)?;
11450        }
11451
11452        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11453        // Separate WITH properties from other properties
11454        let with_properties: Vec<_> = cs
11455            .properties
11456            .iter()
11457            .filter(|p| matches!(p, Expression::Property(_)))
11458            .collect();
11459        let other_properties: Vec<_> = cs
11460            .properties
11461            .iter()
11462            .filter(|p| !matches!(p, Expression::Property(_)))
11463            .collect();
11464
11465        // Generate WITH (props) if we have Property expressions
11466        if !with_properties.is_empty() {
11467            self.write_space();
11468            self.write_keyword("WITH");
11469            self.write(" (");
11470            for (i, prop) in with_properties.iter().enumerate() {
11471                if i > 0 {
11472                    self.write(", ");
11473                }
11474                self.generate_expression(prop)?;
11475            }
11476            self.write(")");
11477        }
11478
11479        // Generate other properties (like DEFAULT COLLATE)
11480        for prop in other_properties {
11481            self.write_space();
11482            self.generate_expression(prop)?;
11483        }
11484
11485        // Restore Athena Hive context
11486        self.athena_hive_context = saved_athena_hive_context;
11487
11488        Ok(())
11489    }
11490
11491    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11492        self.write_keyword("DROP SCHEMA");
11493
11494        if ds.if_exists {
11495            self.write_space();
11496            self.write_keyword("IF EXISTS");
11497        }
11498
11499        self.write_space();
11500        self.generate_identifier(&ds.name)?;
11501
11502        if ds.cascade {
11503            self.write_space();
11504            self.write_keyword("CASCADE");
11505        }
11506
11507        Ok(())
11508    }
11509
11510    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11511        self.write_keyword("DROP NAMESPACE");
11512
11513        if dn.if_exists {
11514            self.write_space();
11515            self.write_keyword("IF EXISTS");
11516        }
11517
11518        self.write_space();
11519        self.generate_identifier(&dn.name)?;
11520
11521        if dn.cascade {
11522            self.write_space();
11523            self.write_keyword("CASCADE");
11524        }
11525
11526        Ok(())
11527    }
11528
11529    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11530        self.write_keyword("CREATE DATABASE");
11531
11532        if cd.if_not_exists {
11533            self.write_space();
11534            self.write_keyword("IF NOT EXISTS");
11535        }
11536
11537        self.write_space();
11538        self.generate_identifier(&cd.name)?;
11539
11540        if let Some(ref clone_src) = cd.clone_from {
11541            self.write_keyword(" CLONE ");
11542            self.generate_identifier(clone_src)?;
11543        }
11544
11545        // AT/BEFORE clause for time travel (Snowflake)
11546        if let Some(ref at_clause) = cd.at_clause {
11547            self.write_space();
11548            self.generate_expression(at_clause)?;
11549        }
11550
11551        for option in &cd.options {
11552            self.write_space();
11553            match option {
11554                DatabaseOption::CharacterSet(charset) => {
11555                    self.write_keyword("CHARACTER SET");
11556                    self.write(" = ");
11557                    self.write(&format!("'{}'", charset));
11558                }
11559                DatabaseOption::Collate(collate) => {
11560                    self.write_keyword("COLLATE");
11561                    self.write(" = ");
11562                    self.write(&format!("'{}'", collate));
11563                }
11564                DatabaseOption::Owner(owner) => {
11565                    self.write_keyword("OWNER");
11566                    self.write(" = ");
11567                    self.generate_identifier(owner)?;
11568                }
11569                DatabaseOption::Template(template) => {
11570                    self.write_keyword("TEMPLATE");
11571                    self.write(" = ");
11572                    self.generate_identifier(template)?;
11573                }
11574                DatabaseOption::Encoding(encoding) => {
11575                    self.write_keyword("ENCODING");
11576                    self.write(" = ");
11577                    self.write(&format!("'{}'", encoding));
11578                }
11579                DatabaseOption::Location(location) => {
11580                    self.write_keyword("LOCATION");
11581                    self.write(" = ");
11582                    self.write(&format!("'{}'", location));
11583                }
11584            }
11585        }
11586
11587        Ok(())
11588    }
11589
11590    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11591        self.write_keyword("DROP DATABASE");
11592
11593        if dd.if_exists {
11594            self.write_space();
11595            self.write_keyword("IF EXISTS");
11596        }
11597
11598        self.write_space();
11599        self.generate_identifier(&dd.name)?;
11600
11601        Ok(())
11602    }
11603
11604    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11605        self.write_keyword("CREATE");
11606
11607        if cf.or_replace {
11608            self.write_space();
11609            self.write_keyword("OR REPLACE");
11610        }
11611
11612        if cf.temporary {
11613            self.write_space();
11614            self.write_keyword("TEMPORARY");
11615        }
11616
11617        self.write_space();
11618        if cf.is_table_function {
11619            self.write_keyword("TABLE FUNCTION");
11620        } else {
11621            self.write_keyword("FUNCTION");
11622        }
11623
11624        if cf.if_not_exists {
11625            self.write_space();
11626            self.write_keyword("IF NOT EXISTS");
11627        }
11628
11629        self.write_space();
11630        self.generate_table(&cf.name)?;
11631        if cf.has_parens {
11632            let func_multiline = self.config.pretty
11633                && matches!(
11634                    self.config.dialect,
11635                    Some(crate::dialects::DialectType::TSQL)
11636                        | Some(crate::dialects::DialectType::Fabric)
11637                )
11638                && !cf.parameters.is_empty();
11639            if func_multiline {
11640                self.write("(\n");
11641                self.indent_level += 2;
11642                self.write_indent();
11643                self.generate_function_parameters(&cf.parameters)?;
11644                self.write("\n");
11645                self.indent_level -= 2;
11646                self.write(")");
11647            } else {
11648                self.write("(");
11649                self.generate_function_parameters(&cf.parameters)?;
11650                self.write(")");
11651            }
11652        }
11653
11654        // Output RETURNS clause (always comes first after parameters)
11655        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11656        let use_multiline = self.config.pretty
11657            && matches!(
11658                self.config.dialect,
11659                Some(crate::dialects::DialectType::BigQuery)
11660                    | Some(crate::dialects::DialectType::TSQL)
11661                    | Some(crate::dialects::DialectType::Fabric)
11662            );
11663
11664        if cf.language_first {
11665            // LANGUAGE first, then SQL data access, then RETURNS
11666            if let Some(lang) = &cf.language {
11667                if use_multiline {
11668                    self.write_newline();
11669                } else {
11670                    self.write_space();
11671                }
11672                self.write_keyword("LANGUAGE");
11673                self.write_space();
11674                self.write(lang);
11675            }
11676
11677            // SQL data access comes after LANGUAGE in this case
11678            if let Some(sql_data) = &cf.sql_data_access {
11679                self.write_space();
11680                match sql_data {
11681                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11682                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11683                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11684                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11685                }
11686            }
11687
11688            if let Some(ref rtb) = cf.returns_table_body {
11689                if use_multiline {
11690                    self.write_newline();
11691                } else {
11692                    self.write_space();
11693                }
11694                self.write_keyword("RETURNS");
11695                self.write_space();
11696                self.write(rtb);
11697            } else if let Some(return_type) = &cf.return_type {
11698                if use_multiline {
11699                    self.write_newline();
11700                } else {
11701                    self.write_space();
11702                }
11703                self.write_keyword("RETURNS");
11704                self.write_space();
11705                self.generate_data_type(return_type)?;
11706            }
11707        } else {
11708            // RETURNS first (default)
11709            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11710            let is_duckdb = matches!(
11711                self.config.dialect,
11712                Some(crate::dialects::DialectType::DuckDB)
11713            );
11714            if let Some(ref rtb) = cf.returns_table_body {
11715                if !(is_duckdb && rtb.is_empty()) {
11716                    if use_multiline {
11717                        self.write_newline();
11718                    } else {
11719                        self.write_space();
11720                    }
11721                    self.write_keyword("RETURNS");
11722                    self.write_space();
11723                    self.write(rtb);
11724                }
11725            } else if let Some(return_type) = &cf.return_type {
11726                if use_multiline {
11727                    self.write_newline();
11728                } else {
11729                    self.write_space();
11730                }
11731                self.write_keyword("RETURNS");
11732                self.write_space();
11733                self.generate_data_type(return_type)?;
11734            }
11735        }
11736
11737        // If we have property_order, use it to output properties in original order
11738        if !cf.property_order.is_empty() {
11739            // For BigQuery, OPTIONS must come before AS - reorder if needed
11740            let is_bigquery = matches!(
11741                self.config.dialect,
11742                Some(crate::dialects::DialectType::BigQuery)
11743            );
11744            let property_order = if is_bigquery {
11745                // Move Options before As if both are present
11746                let mut reordered = Vec::new();
11747                let mut has_as = false;
11748                let mut has_options = false;
11749                for prop in &cf.property_order {
11750                    match prop {
11751                        FunctionPropertyKind::As => has_as = true,
11752                        FunctionPropertyKind::Options => has_options = true,
11753                        _ => {}
11754                    }
11755                }
11756                if has_as && has_options {
11757                    // Output all props except As and Options, then Options, then As
11758                    for prop in &cf.property_order {
11759                        if *prop != FunctionPropertyKind::As
11760                            && *prop != FunctionPropertyKind::Options
11761                        {
11762                            reordered.push(*prop);
11763                        }
11764                    }
11765                    reordered.push(FunctionPropertyKind::Options);
11766                    reordered.push(FunctionPropertyKind::As);
11767                    reordered
11768                } else {
11769                    cf.property_order.clone()
11770                }
11771            } else {
11772                cf.property_order.clone()
11773            };
11774
11775            for prop in &property_order {
11776                match prop {
11777                    FunctionPropertyKind::Set => {
11778                        self.generate_function_set_options(cf)?;
11779                    }
11780                    FunctionPropertyKind::As => {
11781                        self.generate_function_body(cf)?;
11782                    }
11783                    FunctionPropertyKind::Language => {
11784                        if !cf.language_first {
11785                            // Only output here if not already output above
11786                            if let Some(lang) = &cf.language {
11787                                // Only BigQuery uses multiline formatting
11788                                let use_multiline = self.config.pretty
11789                                    && matches!(
11790                                        self.config.dialect,
11791                                        Some(crate::dialects::DialectType::BigQuery)
11792                                    );
11793                                if use_multiline {
11794                                    self.write_newline();
11795                                } else {
11796                                    self.write_space();
11797                                }
11798                                self.write_keyword("LANGUAGE");
11799                                self.write_space();
11800                                self.write(lang);
11801                            }
11802                        }
11803                    }
11804                    FunctionPropertyKind::Determinism => {
11805                        self.generate_function_determinism(cf)?;
11806                    }
11807                    FunctionPropertyKind::NullInput => {
11808                        self.generate_function_null_input(cf)?;
11809                    }
11810                    FunctionPropertyKind::Security => {
11811                        self.generate_function_security(cf)?;
11812                    }
11813                    FunctionPropertyKind::SqlDataAccess => {
11814                        if !cf.language_first {
11815                            // Only output here if not already output above
11816                            self.generate_function_sql_data_access(cf)?;
11817                        }
11818                    }
11819                    FunctionPropertyKind::Options => {
11820                        if !cf.options.is_empty() {
11821                            self.write_space();
11822                            self.generate_options_clause(&cf.options)?;
11823                        }
11824                    }
11825                    FunctionPropertyKind::Environment => {
11826                        if !cf.environment.is_empty() {
11827                            self.write_space();
11828                            self.generate_environment_clause(&cf.environment)?;
11829                        }
11830                    }
11831                }
11832            }
11833
11834            // Output OPTIONS if not tracked in property_order (legacy)
11835            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11836            {
11837                self.write_space();
11838                self.generate_options_clause(&cf.options)?;
11839            }
11840
11841            // Output ENVIRONMENT if not tracked in property_order (legacy)
11842            if !cf.environment.is_empty()
11843                && !cf
11844                    .property_order
11845                    .contains(&FunctionPropertyKind::Environment)
11846            {
11847                self.write_space();
11848                self.generate_environment_clause(&cf.environment)?;
11849            }
11850        } else {
11851            // Legacy behavior when property_order is empty
11852            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11853            if matches!(
11854                self.config.dialect,
11855                Some(crate::dialects::DialectType::BigQuery)
11856            ) {
11857                self.generate_function_determinism(cf)?;
11858            }
11859
11860            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11861            let use_multiline = self.config.pretty
11862                && matches!(
11863                    self.config.dialect,
11864                    Some(crate::dialects::DialectType::BigQuery)
11865                );
11866
11867            if !cf.language_first {
11868                if let Some(lang) = &cf.language {
11869                    if use_multiline {
11870                        self.write_newline();
11871                    } else {
11872                        self.write_space();
11873                    }
11874                    self.write_keyword("LANGUAGE");
11875                    self.write_space();
11876                    self.write(lang);
11877                }
11878
11879                // SQL data access characteristic comes after LANGUAGE
11880                self.generate_function_sql_data_access(cf)?;
11881            }
11882
11883            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11884            if !matches!(
11885                self.config.dialect,
11886                Some(crate::dialects::DialectType::BigQuery)
11887            ) {
11888                self.generate_function_determinism(cf)?;
11889            }
11890
11891            self.generate_function_null_input(cf)?;
11892            self.generate_function_security(cf)?;
11893            self.generate_function_set_options(cf)?;
11894
11895            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11896            if !cf.options.is_empty() {
11897                self.write_space();
11898                self.generate_options_clause(&cf.options)?;
11899            }
11900
11901            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11902            if !cf.environment.is_empty() {
11903                self.write_space();
11904                self.generate_environment_clause(&cf.environment)?;
11905            }
11906
11907            self.generate_function_body(cf)?;
11908        }
11909
11910        Ok(())
11911    }
11912
11913    /// Generate SET options for CREATE FUNCTION
11914    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11915        for opt in &cf.set_options {
11916            self.write_space();
11917            self.write_keyword("SET");
11918            self.write_space();
11919            self.write(&opt.name);
11920            match &opt.value {
11921                FunctionSetValue::Value { value, use_to } => {
11922                    if *use_to {
11923                        self.write(" TO ");
11924                    } else {
11925                        self.write(" = ");
11926                    }
11927                    self.write(value);
11928                }
11929                FunctionSetValue::FromCurrent => {
11930                    self.write_space();
11931                    self.write_keyword("FROM CURRENT");
11932                }
11933            }
11934        }
11935        Ok(())
11936    }
11937
11938    /// Generate function body (AS clause)
11939    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11940        if let Some(body) = &cf.body {
11941            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11942            self.write_space();
11943            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11944            let use_multiline = self.config.pretty
11945                && matches!(
11946                    self.config.dialect,
11947                    Some(crate::dialects::DialectType::BigQuery)
11948                );
11949            match body {
11950                FunctionBody::Block(block) => {
11951                    self.write_keyword("AS");
11952                    if matches!(
11953                        self.config.dialect,
11954                        Some(crate::dialects::DialectType::TSQL)
11955                    ) {
11956                        self.write(" BEGIN ");
11957                        self.write(block);
11958                        self.write(" END");
11959                    } else if matches!(
11960                        self.config.dialect,
11961                        Some(crate::dialects::DialectType::PostgreSQL)
11962                    ) {
11963                        self.write(" $$");
11964                        self.write(block);
11965                        self.write("$$");
11966                    } else {
11967                        // Escape content for single-quoted output
11968                        let escaped = self.escape_block_for_single_quote(block);
11969                        // In BigQuery pretty mode, body content goes on new line
11970                        if use_multiline {
11971                            self.write_newline();
11972                        } else {
11973                            self.write(" ");
11974                        }
11975                        self.write("'");
11976                        self.write(&escaped);
11977                        self.write("'");
11978                    }
11979                }
11980                FunctionBody::StringLiteral(s) => {
11981                    self.write_keyword("AS");
11982                    // In BigQuery pretty mode, body content goes on new line
11983                    if use_multiline {
11984                        self.write_newline();
11985                    } else {
11986                        self.write(" ");
11987                    }
11988                    self.write("'");
11989                    self.write(s);
11990                    self.write("'");
11991                }
11992                FunctionBody::Expression(expr) => {
11993                    self.write_keyword("AS");
11994                    self.write_space();
11995                    self.generate_expression(expr)?;
11996                }
11997                FunctionBody::External(name) => {
11998                    self.write_keyword("EXTERNAL NAME");
11999                    self.write(" '");
12000                    self.write(name);
12001                    self.write("'");
12002                }
12003                FunctionBody::Return(expr) => {
12004                    if matches!(
12005                        self.config.dialect,
12006                        Some(crate::dialects::DialectType::DuckDB)
12007                    ) {
12008                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12009                        self.write_keyword("AS");
12010                        self.write_space();
12011                        // Empty returns_table_body signals TABLE return
12012                        if cf.returns_table_body.is_some() {
12013                            self.write_keyword("TABLE");
12014                            self.write_space();
12015                        }
12016                        self.generate_expression(expr)?;
12017                    } else {
12018                        if self.config.create_function_return_as {
12019                            self.write_keyword("AS");
12020                            // TSQL pretty: newline between AS and RETURN
12021                            if self.config.pretty
12022                                && matches!(
12023                                    self.config.dialect,
12024                                    Some(crate::dialects::DialectType::TSQL)
12025                                        | Some(crate::dialects::DialectType::Fabric)
12026                                )
12027                            {
12028                                self.write_newline();
12029                            } else {
12030                                self.write_space();
12031                            }
12032                        }
12033                        self.write_keyword("RETURN");
12034                        self.write_space();
12035                        self.generate_expression(expr)?;
12036                    }
12037                }
12038                FunctionBody::Statements(stmts) => {
12039                    self.write_keyword("AS");
12040                    self.write(" BEGIN ");
12041                    for (i, stmt) in stmts.iter().enumerate() {
12042                        if i > 0 {
12043                            self.write(" ");
12044                        }
12045                        self.generate_expression(stmt)?;
12046                    }
12047                    self.write(" END");
12048                }
12049                FunctionBody::DollarQuoted { content, tag } => {
12050                    self.write_keyword("AS");
12051                    self.write(" ");
12052                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12053                    let supports_dollar_quoting = matches!(
12054                        self.config.dialect,
12055                        Some(crate::dialects::DialectType::PostgreSQL)
12056                            | Some(crate::dialects::DialectType::Databricks)
12057                            | Some(crate::dialects::DialectType::Redshift)
12058                            | Some(crate::dialects::DialectType::DuckDB)
12059                    );
12060                    if supports_dollar_quoting {
12061                        // Output in dollar-quoted format
12062                        self.write("$");
12063                        if let Some(t) = tag {
12064                            self.write(t);
12065                        }
12066                        self.write("$");
12067                        self.write(content);
12068                        self.write("$");
12069                        if let Some(t) = tag {
12070                            self.write(t);
12071                        }
12072                        self.write("$");
12073                    } else {
12074                        // Convert to single-quoted string for other dialects
12075                        let escaped = self.escape_block_for_single_quote(content);
12076                        self.write("'");
12077                        self.write(&escaped);
12078                        self.write("'");
12079                    }
12080                }
12081            }
12082        }
12083        Ok(())
12084    }
12085
12086    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12087    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12088        if let Some(det) = cf.deterministic {
12089            self.write_space();
12090            if matches!(
12091                self.config.dialect,
12092                Some(crate::dialects::DialectType::BigQuery)
12093            ) {
12094                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12095                if det {
12096                    self.write_keyword("DETERMINISTIC");
12097                } else {
12098                    self.write_keyword("NOT DETERMINISTIC");
12099                }
12100            } else {
12101                // PostgreSQL and others use IMMUTABLE/VOLATILE
12102                if det {
12103                    self.write_keyword("IMMUTABLE");
12104                } else {
12105                    self.write_keyword("VOLATILE");
12106                }
12107            }
12108        }
12109        Ok(())
12110    }
12111
12112    /// Generate null input handling clause
12113    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12114        if let Some(returns_null) = cf.returns_null_on_null_input {
12115            self.write_space();
12116            if returns_null {
12117                if cf.strict {
12118                    self.write_keyword("STRICT");
12119                } else {
12120                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12121                }
12122            } else {
12123                self.write_keyword("CALLED ON NULL INPUT");
12124            }
12125        }
12126        Ok(())
12127    }
12128
12129    /// Generate security clause
12130    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12131        if let Some(security) = &cf.security {
12132            self.write_space();
12133            self.write_keyword("SECURITY");
12134            self.write_space();
12135            match security {
12136                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12137                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12138                FunctionSecurity::None => self.write_keyword("NONE"),
12139            }
12140        }
12141        Ok(())
12142    }
12143
12144    /// Generate SQL data access clause
12145    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12146        if let Some(sql_data) = &cf.sql_data_access {
12147            self.write_space();
12148            match sql_data {
12149                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12150                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12151                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12152                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12153            }
12154        }
12155        Ok(())
12156    }
12157
12158    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12159        for (i, param) in params.iter().enumerate() {
12160            if i > 0 {
12161                self.write(", ");
12162            }
12163
12164            if let Some(mode) = &param.mode {
12165                if let Some(text) = &param.mode_text {
12166                    self.write(text);
12167                } else {
12168                    match mode {
12169                        ParameterMode::In => self.write_keyword("IN"),
12170                        ParameterMode::Out => self.write_keyword("OUT"),
12171                        ParameterMode::InOut => self.write_keyword("INOUT"),
12172                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12173                    }
12174                }
12175                self.write_space();
12176            }
12177
12178            if let Some(name) = &param.name {
12179                self.generate_identifier(name)?;
12180                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12181                let skip_type =
12182                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12183                if !skip_type {
12184                    self.write_space();
12185                    self.generate_data_type(&param.data_type)?;
12186                }
12187            } else {
12188                self.generate_data_type(&param.data_type)?;
12189            }
12190
12191            if let Some(default) = &param.default {
12192                if self.config.parameter_default_equals {
12193                    self.write(" = ");
12194                } else {
12195                    self.write(" DEFAULT ");
12196                }
12197                self.generate_expression(default)?;
12198            }
12199        }
12200
12201        Ok(())
12202    }
12203
12204    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12205        self.write_keyword("DROP FUNCTION");
12206
12207        if df.if_exists {
12208            self.write_space();
12209            self.write_keyword("IF EXISTS");
12210        }
12211
12212        self.write_space();
12213        self.generate_table(&df.name)?;
12214
12215        if let Some(params) = &df.parameters {
12216            self.write(" (");
12217            for (i, dt) in params.iter().enumerate() {
12218                if i > 0 {
12219                    self.write(", ");
12220                }
12221                self.generate_data_type(dt)?;
12222            }
12223            self.write(")");
12224        }
12225
12226        if df.cascade {
12227            self.write_space();
12228            self.write_keyword("CASCADE");
12229        }
12230
12231        Ok(())
12232    }
12233
12234    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12235        self.write_keyword("CREATE");
12236
12237        if cp.or_replace {
12238            self.write_space();
12239            self.write_keyword("OR REPLACE");
12240        }
12241
12242        self.write_space();
12243        if cp.use_proc_keyword {
12244            self.write_keyword("PROC");
12245        } else {
12246            self.write_keyword("PROCEDURE");
12247        }
12248
12249        if cp.if_not_exists {
12250            self.write_space();
12251            self.write_keyword("IF NOT EXISTS");
12252        }
12253
12254        self.write_space();
12255        self.generate_table(&cp.name)?;
12256        if cp.has_parens {
12257            self.write("(");
12258            self.generate_function_parameters(&cp.parameters)?;
12259            self.write(")");
12260        } else if !cp.parameters.is_empty() {
12261            // TSQL: unparenthesized parameters
12262            self.write_space();
12263            self.generate_function_parameters(&cp.parameters)?;
12264        }
12265
12266        // RETURNS clause (Snowflake)
12267        if let Some(return_type) = &cp.return_type {
12268            self.write_space();
12269            self.write_keyword("RETURNS");
12270            self.write_space();
12271            self.generate_data_type(return_type)?;
12272        }
12273
12274        // EXECUTE AS clause (Snowflake)
12275        if let Some(execute_as) = &cp.execute_as {
12276            self.write_space();
12277            self.write_keyword("EXECUTE AS");
12278            self.write_space();
12279            self.write_keyword(execute_as);
12280        }
12281
12282        if let Some(lang) = &cp.language {
12283            self.write_space();
12284            self.write_keyword("LANGUAGE");
12285            self.write_space();
12286            self.write(lang);
12287        }
12288
12289        if let Some(security) = &cp.security {
12290            self.write_space();
12291            self.write_keyword("SECURITY");
12292            self.write_space();
12293            match security {
12294                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12295                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12296                FunctionSecurity::None => self.write_keyword("NONE"),
12297            }
12298        }
12299
12300        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12301        if !cp.with_options.is_empty() {
12302            self.write_space();
12303            self.write_keyword("WITH");
12304            self.write_space();
12305            for (i, opt) in cp.with_options.iter().enumerate() {
12306                if i > 0 {
12307                    self.write(", ");
12308                }
12309                self.write(opt);
12310            }
12311        }
12312
12313        if let Some(body) = &cp.body {
12314            self.write_space();
12315            match body {
12316                FunctionBody::Block(block) => {
12317                    self.write_keyword("AS");
12318                    if matches!(
12319                        self.config.dialect,
12320                        Some(crate::dialects::DialectType::TSQL)
12321                    ) {
12322                        self.write(" BEGIN ");
12323                        self.write(block);
12324                        self.write(" END");
12325                    } else if matches!(
12326                        self.config.dialect,
12327                        Some(crate::dialects::DialectType::PostgreSQL)
12328                    ) {
12329                        self.write(" $$");
12330                        self.write(block);
12331                        self.write("$$");
12332                    } else {
12333                        // Escape content for single-quoted output
12334                        let escaped = self.escape_block_for_single_quote(block);
12335                        self.write(" '");
12336                        self.write(&escaped);
12337                        self.write("'");
12338                    }
12339                }
12340                FunctionBody::StringLiteral(s) => {
12341                    self.write_keyword("AS");
12342                    self.write(" '");
12343                    self.write(s);
12344                    self.write("'");
12345                }
12346                FunctionBody::Expression(expr) => {
12347                    self.write_keyword("AS");
12348                    self.write_space();
12349                    self.generate_expression(expr)?;
12350                }
12351                FunctionBody::External(name) => {
12352                    self.write_keyword("EXTERNAL NAME");
12353                    self.write(" '");
12354                    self.write(name);
12355                    self.write("'");
12356                }
12357                FunctionBody::Return(expr) => {
12358                    self.write_keyword("RETURN");
12359                    self.write_space();
12360                    self.generate_expression(expr)?;
12361                }
12362                FunctionBody::Statements(stmts) => {
12363                    self.write_keyword("AS");
12364                    self.write(" BEGIN ");
12365                    for (i, stmt) in stmts.iter().enumerate() {
12366                        if i > 0 {
12367                            self.write(" ");
12368                        }
12369                        self.generate_expression(stmt)?;
12370                    }
12371                    self.write(" END");
12372                }
12373                FunctionBody::DollarQuoted { content, tag } => {
12374                    self.write_keyword("AS");
12375                    self.write(" ");
12376                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12377                    let supports_dollar_quoting = matches!(
12378                        self.config.dialect,
12379                        Some(crate::dialects::DialectType::PostgreSQL)
12380                            | Some(crate::dialects::DialectType::Databricks)
12381                            | Some(crate::dialects::DialectType::Redshift)
12382                            | Some(crate::dialects::DialectType::DuckDB)
12383                    );
12384                    if supports_dollar_quoting {
12385                        // Output in dollar-quoted format
12386                        self.write("$");
12387                        if let Some(t) = tag {
12388                            self.write(t);
12389                        }
12390                        self.write("$");
12391                        self.write(content);
12392                        self.write("$");
12393                        if let Some(t) = tag {
12394                            self.write(t);
12395                        }
12396                        self.write("$");
12397                    } else {
12398                        // Convert to single-quoted string for other dialects
12399                        let escaped = self.escape_block_for_single_quote(content);
12400                        self.write("'");
12401                        self.write(&escaped);
12402                        self.write("'");
12403                    }
12404                }
12405            }
12406        }
12407
12408        Ok(())
12409    }
12410
12411    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12412        self.write_keyword("DROP PROCEDURE");
12413
12414        if dp.if_exists {
12415            self.write_space();
12416            self.write_keyword("IF EXISTS");
12417        }
12418
12419        self.write_space();
12420        self.generate_table(&dp.name)?;
12421
12422        if let Some(params) = &dp.parameters {
12423            self.write(" (");
12424            for (i, dt) in params.iter().enumerate() {
12425                if i > 0 {
12426                    self.write(", ");
12427                }
12428                self.generate_data_type(dt)?;
12429            }
12430            self.write(")");
12431        }
12432
12433        if dp.cascade {
12434            self.write_space();
12435            self.write_keyword("CASCADE");
12436        }
12437
12438        Ok(())
12439    }
12440
12441    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12442        self.write_keyword("CREATE");
12443
12444        if cs.or_replace {
12445            self.write_space();
12446            self.write_keyword("OR REPLACE");
12447        }
12448
12449        if cs.temporary {
12450            self.write_space();
12451            self.write_keyword("TEMPORARY");
12452        }
12453
12454        self.write_space();
12455        self.write_keyword("SEQUENCE");
12456
12457        if cs.if_not_exists {
12458            self.write_space();
12459            self.write_keyword("IF NOT EXISTS");
12460        }
12461
12462        self.write_space();
12463        self.generate_table(&cs.name)?;
12464
12465        // Output AS <type> if present
12466        if let Some(as_type) = &cs.as_type {
12467            self.write_space();
12468            self.write_keyword("AS");
12469            self.write_space();
12470            self.generate_data_type(as_type)?;
12471        }
12472
12473        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12474        if let Some(comment) = &cs.comment {
12475            self.write_space();
12476            self.write_keyword("COMMENT");
12477            self.write("=");
12478            self.generate_string_literal(comment)?;
12479        }
12480
12481        // If property_order is available, use it to preserve original order
12482        if !cs.property_order.is_empty() {
12483            for prop in &cs.property_order {
12484                match prop {
12485                    SeqPropKind::Start => {
12486                        if let Some(start) = cs.start {
12487                            self.write_space();
12488                            self.write_keyword("START WITH");
12489                            self.write(&format!(" {}", start));
12490                        }
12491                    }
12492                    SeqPropKind::Increment => {
12493                        if let Some(inc) = cs.increment {
12494                            self.write_space();
12495                            self.write_keyword("INCREMENT BY");
12496                            self.write(&format!(" {}", inc));
12497                        }
12498                    }
12499                    SeqPropKind::Minvalue => {
12500                        if let Some(min) = &cs.minvalue {
12501                            self.write_space();
12502                            match min {
12503                                SequenceBound::Value(v) => {
12504                                    self.write_keyword("MINVALUE");
12505                                    self.write(&format!(" {}", v));
12506                                }
12507                                SequenceBound::None => {
12508                                    self.write_keyword("NO MINVALUE");
12509                                }
12510                            }
12511                        }
12512                    }
12513                    SeqPropKind::Maxvalue => {
12514                        if let Some(max) = &cs.maxvalue {
12515                            self.write_space();
12516                            match max {
12517                                SequenceBound::Value(v) => {
12518                                    self.write_keyword("MAXVALUE");
12519                                    self.write(&format!(" {}", v));
12520                                }
12521                                SequenceBound::None => {
12522                                    self.write_keyword("NO MAXVALUE");
12523                                }
12524                            }
12525                        }
12526                    }
12527                    SeqPropKind::Cache => {
12528                        if let Some(cache) = cs.cache {
12529                            self.write_space();
12530                            self.write_keyword("CACHE");
12531                            self.write(&format!(" {}", cache));
12532                        }
12533                    }
12534                    SeqPropKind::NoCache => {
12535                        self.write_space();
12536                        self.write_keyword("NO CACHE");
12537                    }
12538                    SeqPropKind::NoCacheWord => {
12539                        self.write_space();
12540                        self.write_keyword("NOCACHE");
12541                    }
12542                    SeqPropKind::Cycle => {
12543                        self.write_space();
12544                        self.write_keyword("CYCLE");
12545                    }
12546                    SeqPropKind::NoCycle => {
12547                        self.write_space();
12548                        self.write_keyword("NO CYCLE");
12549                    }
12550                    SeqPropKind::NoCycleWord => {
12551                        self.write_space();
12552                        self.write_keyword("NOCYCLE");
12553                    }
12554                    SeqPropKind::OwnedBy => {
12555                        // Skip OWNED BY NONE (it's a no-op)
12556                        if !cs.owned_by_none {
12557                            if let Some(owned) = &cs.owned_by {
12558                                self.write_space();
12559                                self.write_keyword("OWNED BY");
12560                                self.write_space();
12561                                self.generate_table(owned)?;
12562                            }
12563                        }
12564                    }
12565                    SeqPropKind::Order => {
12566                        self.write_space();
12567                        self.write_keyword("ORDER");
12568                    }
12569                    SeqPropKind::NoOrder => {
12570                        self.write_space();
12571                        self.write_keyword("NOORDER");
12572                    }
12573                    SeqPropKind::Comment => {
12574                        // COMMENT is output above, before property_order iteration
12575                    }
12576                    SeqPropKind::Sharing => {
12577                        if let Some(val) = &cs.sharing {
12578                            self.write_space();
12579                            self.write(&format!("SHARING={}", val));
12580                        }
12581                    }
12582                    SeqPropKind::Keep => {
12583                        self.write_space();
12584                        self.write_keyword("KEEP");
12585                    }
12586                    SeqPropKind::NoKeep => {
12587                        self.write_space();
12588                        self.write_keyword("NOKEEP");
12589                    }
12590                    SeqPropKind::Scale => {
12591                        self.write_space();
12592                        self.write_keyword("SCALE");
12593                        if let Some(modifier) = &cs.scale_modifier {
12594                            if !modifier.is_empty() {
12595                                self.write_space();
12596                                self.write_keyword(modifier);
12597                            }
12598                        }
12599                    }
12600                    SeqPropKind::NoScale => {
12601                        self.write_space();
12602                        self.write_keyword("NOSCALE");
12603                    }
12604                    SeqPropKind::Shard => {
12605                        self.write_space();
12606                        self.write_keyword("SHARD");
12607                        if let Some(modifier) = &cs.shard_modifier {
12608                            if !modifier.is_empty() {
12609                                self.write_space();
12610                                self.write_keyword(modifier);
12611                            }
12612                        }
12613                    }
12614                    SeqPropKind::NoShard => {
12615                        self.write_space();
12616                        self.write_keyword("NOSHARD");
12617                    }
12618                    SeqPropKind::Session => {
12619                        self.write_space();
12620                        self.write_keyword("SESSION");
12621                    }
12622                    SeqPropKind::Global => {
12623                        self.write_space();
12624                        self.write_keyword("GLOBAL");
12625                    }
12626                    SeqPropKind::NoMinvalueWord => {
12627                        self.write_space();
12628                        self.write_keyword("NOMINVALUE");
12629                    }
12630                    SeqPropKind::NoMaxvalueWord => {
12631                        self.write_space();
12632                        self.write_keyword("NOMAXVALUE");
12633                    }
12634                }
12635            }
12636        } else {
12637            // Fallback: default order for backwards compatibility
12638            if let Some(inc) = cs.increment {
12639                self.write_space();
12640                self.write_keyword("INCREMENT BY");
12641                self.write(&format!(" {}", inc));
12642            }
12643
12644            if let Some(min) = &cs.minvalue {
12645                self.write_space();
12646                match min {
12647                    SequenceBound::Value(v) => {
12648                        self.write_keyword("MINVALUE");
12649                        self.write(&format!(" {}", v));
12650                    }
12651                    SequenceBound::None => {
12652                        self.write_keyword("NO MINVALUE");
12653                    }
12654                }
12655            }
12656
12657            if let Some(max) = &cs.maxvalue {
12658                self.write_space();
12659                match max {
12660                    SequenceBound::Value(v) => {
12661                        self.write_keyword("MAXVALUE");
12662                        self.write(&format!(" {}", v));
12663                    }
12664                    SequenceBound::None => {
12665                        self.write_keyword("NO MAXVALUE");
12666                    }
12667                }
12668            }
12669
12670            if let Some(start) = cs.start {
12671                self.write_space();
12672                self.write_keyword("START WITH");
12673                self.write(&format!(" {}", start));
12674            }
12675
12676            if let Some(cache) = cs.cache {
12677                self.write_space();
12678                self.write_keyword("CACHE");
12679                self.write(&format!(" {}", cache));
12680            }
12681
12682            if cs.cycle {
12683                self.write_space();
12684                self.write_keyword("CYCLE");
12685            }
12686
12687            if let Some(owned) = &cs.owned_by {
12688                self.write_space();
12689                self.write_keyword("OWNED BY");
12690                self.write_space();
12691                self.generate_table(owned)?;
12692            }
12693        }
12694
12695        Ok(())
12696    }
12697
12698    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12699        self.write_keyword("DROP SEQUENCE");
12700
12701        if ds.if_exists {
12702            self.write_space();
12703            self.write_keyword("IF EXISTS");
12704        }
12705
12706        self.write_space();
12707        self.generate_table(&ds.name)?;
12708
12709        if ds.cascade {
12710            self.write_space();
12711            self.write_keyword("CASCADE");
12712        }
12713
12714        Ok(())
12715    }
12716
12717    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12718        self.write_keyword("ALTER SEQUENCE");
12719
12720        if als.if_exists {
12721            self.write_space();
12722            self.write_keyword("IF EXISTS");
12723        }
12724
12725        self.write_space();
12726        self.generate_table(&als.name)?;
12727
12728        if let Some(inc) = als.increment {
12729            self.write_space();
12730            self.write_keyword("INCREMENT BY");
12731            self.write(&format!(" {}", inc));
12732        }
12733
12734        if let Some(min) = &als.minvalue {
12735            self.write_space();
12736            match min {
12737                SequenceBound::Value(v) => {
12738                    self.write_keyword("MINVALUE");
12739                    self.write(&format!(" {}", v));
12740                }
12741                SequenceBound::None => {
12742                    self.write_keyword("NO MINVALUE");
12743                }
12744            }
12745        }
12746
12747        if let Some(max) = &als.maxvalue {
12748            self.write_space();
12749            match max {
12750                SequenceBound::Value(v) => {
12751                    self.write_keyword("MAXVALUE");
12752                    self.write(&format!(" {}", v));
12753                }
12754                SequenceBound::None => {
12755                    self.write_keyword("NO MAXVALUE");
12756                }
12757            }
12758        }
12759
12760        if let Some(start) = als.start {
12761            self.write_space();
12762            self.write_keyword("START WITH");
12763            self.write(&format!(" {}", start));
12764        }
12765
12766        if let Some(restart) = &als.restart {
12767            self.write_space();
12768            self.write_keyword("RESTART");
12769            if let Some(val) = restart {
12770                self.write_keyword(" WITH");
12771                self.write(&format!(" {}", val));
12772            }
12773        }
12774
12775        if let Some(cache) = als.cache {
12776            self.write_space();
12777            self.write_keyword("CACHE");
12778            self.write(&format!(" {}", cache));
12779        }
12780
12781        if let Some(cycle) = als.cycle {
12782            self.write_space();
12783            if cycle {
12784                self.write_keyword("CYCLE");
12785            } else {
12786                self.write_keyword("NO CYCLE");
12787            }
12788        }
12789
12790        if let Some(owned) = &als.owned_by {
12791            self.write_space();
12792            self.write_keyword("OWNED BY");
12793            self.write_space();
12794            if let Some(table) = owned {
12795                self.generate_table(table)?;
12796            } else {
12797                self.write_keyword("NONE");
12798            }
12799        }
12800
12801        Ok(())
12802    }
12803
12804    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12805        self.write_keyword("CREATE");
12806
12807        if ct.or_replace {
12808            self.write_space();
12809            self.write_keyword("OR REPLACE");
12810        }
12811
12812        if ct.constraint {
12813            self.write_space();
12814            self.write_keyword("CONSTRAINT");
12815        }
12816
12817        self.write_space();
12818        self.write_keyword("TRIGGER");
12819        self.write_space();
12820        self.generate_identifier(&ct.name)?;
12821
12822        self.write_space();
12823        match ct.timing {
12824            TriggerTiming::Before => self.write_keyword("BEFORE"),
12825            TriggerTiming::After => self.write_keyword("AFTER"),
12826            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12827        }
12828
12829        // Events
12830        for (i, event) in ct.events.iter().enumerate() {
12831            if i > 0 {
12832                self.write_keyword(" OR");
12833            }
12834            self.write_space();
12835            match event {
12836                TriggerEvent::Insert => self.write_keyword("INSERT"),
12837                TriggerEvent::Update(cols) => {
12838                    self.write_keyword("UPDATE");
12839                    if let Some(cols) = cols {
12840                        self.write_space();
12841                        self.write_keyword("OF");
12842                        for (j, col) in cols.iter().enumerate() {
12843                            if j > 0 {
12844                                self.write(",");
12845                            }
12846                            self.write_space();
12847                            self.generate_identifier(col)?;
12848                        }
12849                    }
12850                }
12851                TriggerEvent::Delete => self.write_keyword("DELETE"),
12852                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12853            }
12854        }
12855
12856        self.write_space();
12857        self.write_keyword("ON");
12858        self.write_space();
12859        self.generate_table(&ct.table)?;
12860
12861        // Referencing clause
12862        if let Some(ref_clause) = &ct.referencing {
12863            self.write_space();
12864            self.write_keyword("REFERENCING");
12865            if let Some(old_table) = &ref_clause.old_table {
12866                self.write_space();
12867                self.write_keyword("OLD TABLE AS");
12868                self.write_space();
12869                self.generate_identifier(old_table)?;
12870            }
12871            if let Some(new_table) = &ref_clause.new_table {
12872                self.write_space();
12873                self.write_keyword("NEW TABLE AS");
12874                self.write_space();
12875                self.generate_identifier(new_table)?;
12876            }
12877            if let Some(old_row) = &ref_clause.old_row {
12878                self.write_space();
12879                self.write_keyword("OLD ROW AS");
12880                self.write_space();
12881                self.generate_identifier(old_row)?;
12882            }
12883            if let Some(new_row) = &ref_clause.new_row {
12884                self.write_space();
12885                self.write_keyword("NEW ROW AS");
12886                self.write_space();
12887                self.generate_identifier(new_row)?;
12888            }
12889        }
12890
12891        // Deferrable options for constraint triggers (must come before FOR EACH)
12892        if let Some(deferrable) = ct.deferrable {
12893            self.write_space();
12894            if deferrable {
12895                self.write_keyword("DEFERRABLE");
12896            } else {
12897                self.write_keyword("NOT DEFERRABLE");
12898            }
12899        }
12900
12901        if let Some(initially) = ct.initially_deferred {
12902            self.write_space();
12903            self.write_keyword("INITIALLY");
12904            self.write_space();
12905            if initially {
12906                self.write_keyword("DEFERRED");
12907            } else {
12908                self.write_keyword("IMMEDIATE");
12909            }
12910        }
12911
12912        self.write_space();
12913        self.write_keyword("FOR EACH");
12914        self.write_space();
12915        match ct.for_each {
12916            TriggerForEach::Row => self.write_keyword("ROW"),
12917            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12918        }
12919
12920        // When clause
12921        if let Some(when) = &ct.when {
12922            self.write_space();
12923            self.write_keyword("WHEN");
12924            self.write(" (");
12925            self.generate_expression(when)?;
12926            self.write(")");
12927        }
12928
12929        // Body
12930        self.write_space();
12931        match &ct.body {
12932            TriggerBody::Execute { function, args } => {
12933                self.write_keyword("EXECUTE FUNCTION");
12934                self.write_space();
12935                self.generate_table(function)?;
12936                self.write("(");
12937                for (i, arg) in args.iter().enumerate() {
12938                    if i > 0 {
12939                        self.write(", ");
12940                    }
12941                    self.generate_expression(arg)?;
12942                }
12943                self.write(")");
12944            }
12945            TriggerBody::Block(block) => {
12946                self.write_keyword("BEGIN");
12947                self.write_space();
12948                self.write(block);
12949                self.write_space();
12950                self.write_keyword("END");
12951            }
12952        }
12953
12954        Ok(())
12955    }
12956
12957    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12958        self.write_keyword("DROP TRIGGER");
12959
12960        if dt.if_exists {
12961            self.write_space();
12962            self.write_keyword("IF EXISTS");
12963        }
12964
12965        self.write_space();
12966        self.generate_identifier(&dt.name)?;
12967
12968        if let Some(table) = &dt.table {
12969            self.write_space();
12970            self.write_keyword("ON");
12971            self.write_space();
12972            self.generate_table(table)?;
12973        }
12974
12975        if dt.cascade {
12976            self.write_space();
12977            self.write_keyword("CASCADE");
12978        }
12979
12980        Ok(())
12981    }
12982
12983    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12984        self.write_keyword("CREATE TYPE");
12985
12986        if ct.if_not_exists {
12987            self.write_space();
12988            self.write_keyword("IF NOT EXISTS");
12989        }
12990
12991        self.write_space();
12992        self.generate_table(&ct.name)?;
12993
12994        self.write_space();
12995        self.write_keyword("AS");
12996        self.write_space();
12997
12998        match &ct.definition {
12999            TypeDefinition::Enum(values) => {
13000                self.write_keyword("ENUM");
13001                self.write(" (");
13002                for (i, val) in values.iter().enumerate() {
13003                    if i > 0 {
13004                        self.write(", ");
13005                    }
13006                    self.write(&format!("'{}'", val));
13007                }
13008                self.write(")");
13009            }
13010            TypeDefinition::Composite(attrs) => {
13011                self.write("(");
13012                for (i, attr) in attrs.iter().enumerate() {
13013                    if i > 0 {
13014                        self.write(", ");
13015                    }
13016                    self.generate_identifier(&attr.name)?;
13017                    self.write_space();
13018                    self.generate_data_type(&attr.data_type)?;
13019                    if let Some(collate) = &attr.collate {
13020                        self.write_space();
13021                        self.write_keyword("COLLATE");
13022                        self.write_space();
13023                        self.generate_identifier(collate)?;
13024                    }
13025                }
13026                self.write(")");
13027            }
13028            TypeDefinition::Range {
13029                subtype,
13030                subtype_diff,
13031                canonical,
13032            } => {
13033                self.write_keyword("RANGE");
13034                self.write(" (");
13035                self.write_keyword("SUBTYPE");
13036                self.write(" = ");
13037                self.generate_data_type(subtype)?;
13038                if let Some(diff) = subtype_diff {
13039                    self.write(", ");
13040                    self.write_keyword("SUBTYPE_DIFF");
13041                    self.write(" = ");
13042                    self.write(diff);
13043                }
13044                if let Some(canon) = canonical {
13045                    self.write(", ");
13046                    self.write_keyword("CANONICAL");
13047                    self.write(" = ");
13048                    self.write(canon);
13049                }
13050                self.write(")");
13051            }
13052            TypeDefinition::Base {
13053                input,
13054                output,
13055                internallength,
13056            } => {
13057                self.write("(");
13058                self.write_keyword("INPUT");
13059                self.write(" = ");
13060                self.write(input);
13061                self.write(", ");
13062                self.write_keyword("OUTPUT");
13063                self.write(" = ");
13064                self.write(output);
13065                if let Some(len) = internallength {
13066                    self.write(", ");
13067                    self.write_keyword("INTERNALLENGTH");
13068                    self.write(" = ");
13069                    self.write(&len.to_string());
13070                }
13071                self.write(")");
13072            }
13073            TypeDefinition::Domain {
13074                base_type,
13075                default,
13076                constraints,
13077            } => {
13078                self.generate_data_type(base_type)?;
13079                if let Some(def) = default {
13080                    self.write_space();
13081                    self.write_keyword("DEFAULT");
13082                    self.write_space();
13083                    self.generate_expression(def)?;
13084                }
13085                for constr in constraints {
13086                    self.write_space();
13087                    if let Some(name) = &constr.name {
13088                        self.write_keyword("CONSTRAINT");
13089                        self.write_space();
13090                        self.generate_identifier(name)?;
13091                        self.write_space();
13092                    }
13093                    self.write_keyword("CHECK");
13094                    self.write(" (");
13095                    self.generate_expression(&constr.check)?;
13096                    self.write(")");
13097                }
13098            }
13099        }
13100
13101        Ok(())
13102    }
13103
13104    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13105        self.write_keyword("DROP TYPE");
13106
13107        if dt.if_exists {
13108            self.write_space();
13109            self.write_keyword("IF EXISTS");
13110        }
13111
13112        self.write_space();
13113        self.generate_table(&dt.name)?;
13114
13115        if dt.cascade {
13116            self.write_space();
13117            self.write_keyword("CASCADE");
13118        }
13119
13120        Ok(())
13121    }
13122
13123    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13124        // Athena: DESCRIBE uses Hive engine (backticks)
13125        let saved_athena_hive_context = self.athena_hive_context;
13126        if matches!(
13127            self.config.dialect,
13128            Some(crate::dialects::DialectType::Athena)
13129        ) {
13130            self.athena_hive_context = true;
13131        }
13132
13133        // Output leading comments before DESCRIBE
13134        for comment in &d.leading_comments {
13135            self.write_formatted_comment(comment);
13136            self.write(" ");
13137        }
13138
13139        self.write_keyword("DESCRIBE");
13140
13141        if d.extended {
13142            self.write_space();
13143            self.write_keyword("EXTENDED");
13144        } else if d.formatted {
13145            self.write_space();
13146            self.write_keyword("FORMATTED");
13147        }
13148
13149        // Output style like ANALYZE, HISTORY
13150        if let Some(ref style) = d.style {
13151            self.write_space();
13152            self.write_keyword(style);
13153        }
13154
13155        // Handle object kind (TABLE, VIEW) based on dialect
13156        let should_output_kind = match self.config.dialect {
13157            // Spark doesn't use TABLE/VIEW after DESCRIBE
13158            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13159                false
13160            }
13161            // Snowflake always includes TABLE
13162            Some(DialectType::Snowflake) => true,
13163            _ => d.kind.is_some(),
13164        };
13165        if should_output_kind {
13166            if let Some(ref kind) = d.kind {
13167                self.write_space();
13168                self.write_keyword(kind);
13169            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13170                self.write_space();
13171                self.write_keyword("TABLE");
13172            }
13173        }
13174
13175        self.write_space();
13176        self.generate_expression(&d.target)?;
13177
13178        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13179        if let Some(ref partition) = d.partition {
13180            self.write_space();
13181            self.generate_expression(partition)?;
13182        }
13183
13184        // Databricks: AS JSON
13185        if d.as_json {
13186            self.write_space();
13187            self.write_keyword("AS JSON");
13188        }
13189
13190        // Output properties like type=stage
13191        for (name, value) in &d.properties {
13192            self.write_space();
13193            self.write(name);
13194            self.write("=");
13195            self.write(value);
13196        }
13197
13198        // Restore Athena Hive context
13199        self.athena_hive_context = saved_athena_hive_context;
13200
13201        Ok(())
13202    }
13203
13204    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13205    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13206    fn generate_show(&mut self, s: &Show) -> Result<()> {
13207        self.write_keyword("SHOW");
13208        self.write_space();
13209
13210        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13211        // where TERSE is syntactically valid but has no effect on output
13212        let show_terse = s.terse
13213            && !matches!(
13214                s.this.as_str(),
13215                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13216            );
13217        if show_terse {
13218            self.write_keyword("TERSE");
13219            self.write_space();
13220        }
13221
13222        // Object type (USERS, TABLES, DATABASES, etc.)
13223        self.write_keyword(&s.this);
13224
13225        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13226        if let Some(ref target_expr) = s.target {
13227            self.write_space();
13228            self.generate_expression(target_expr)?;
13229        }
13230
13231        // HISTORY keyword
13232        if s.history {
13233            self.write_space();
13234            self.write_keyword("HISTORY");
13235        }
13236
13237        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13238        if let Some(ref for_target) = s.for_target {
13239            self.write_space();
13240            self.write_keyword("FOR");
13241            self.write_space();
13242            self.generate_expression(for_target)?;
13243        }
13244
13245        // Determine ordering based on dialect:
13246        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13247        // - MySQL: IN, FROM, LIKE (when FROM is present)
13248        use crate::dialects::DialectType;
13249        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13250
13251        if !is_snowflake && s.from.is_some() {
13252            // MySQL ordering: IN, FROM, LIKE
13253
13254            // IN scope_kind [scope]
13255            if let Some(ref scope_kind) = s.scope_kind {
13256                self.write_space();
13257                self.write_keyword("IN");
13258                self.write_space();
13259                self.write_keyword(scope_kind);
13260                if let Some(ref scope) = s.scope {
13261                    self.write_space();
13262                    self.generate_expression(scope)?;
13263                }
13264            } else if let Some(ref scope) = s.scope {
13265                self.write_space();
13266                self.write_keyword("IN");
13267                self.write_space();
13268                self.generate_expression(scope)?;
13269            }
13270
13271            // FROM clause
13272            if let Some(ref from) = s.from {
13273                self.write_space();
13274                self.write_keyword("FROM");
13275                self.write_space();
13276                self.generate_expression(from)?;
13277            }
13278
13279            // Second FROM clause (db name)
13280            if let Some(ref db) = s.db {
13281                self.write_space();
13282                self.write_keyword("FROM");
13283                self.write_space();
13284                self.generate_expression(db)?;
13285            }
13286
13287            // LIKE pattern
13288            if let Some(ref like) = s.like {
13289                self.write_space();
13290                self.write_keyword("LIKE");
13291                self.write_space();
13292                self.generate_expression(like)?;
13293            }
13294        } else {
13295            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13296
13297            // LIKE pattern
13298            if let Some(ref like) = s.like {
13299                self.write_space();
13300                self.write_keyword("LIKE");
13301                self.write_space();
13302                self.generate_expression(like)?;
13303            }
13304
13305            // IN scope_kind [scope]
13306            if let Some(ref scope_kind) = s.scope_kind {
13307                self.write_space();
13308                self.write_keyword("IN");
13309                self.write_space();
13310                self.write_keyword(scope_kind);
13311                if let Some(ref scope) = s.scope {
13312                    self.write_space();
13313                    self.generate_expression(scope)?;
13314                }
13315            } else if let Some(ref scope) = s.scope {
13316                self.write_space();
13317                self.write_keyword("IN");
13318                self.write_space();
13319                self.generate_expression(scope)?;
13320            }
13321        }
13322
13323        // STARTS WITH pattern
13324        if let Some(ref starts_with) = s.starts_with {
13325            self.write_space();
13326            self.write_keyword("STARTS WITH");
13327            self.write_space();
13328            self.generate_expression(starts_with)?;
13329        }
13330
13331        // LIMIT clause
13332        if let Some(ref limit) = s.limit {
13333            self.write_space();
13334            self.generate_limit(limit)?;
13335        }
13336
13337        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13338        if is_snowflake {
13339            if let Some(ref from) = s.from {
13340                self.write_space();
13341                self.write_keyword("FROM");
13342                self.write_space();
13343                self.generate_expression(from)?;
13344            }
13345        }
13346
13347        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13348        if let Some(ref where_clause) = s.where_clause {
13349            self.write_space();
13350            self.write_keyword("WHERE");
13351            self.write_space();
13352            self.generate_expression(where_clause)?;
13353        }
13354
13355        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13356        if let Some(is_mutex) = s.mutex {
13357            self.write_space();
13358            if is_mutex {
13359                self.write_keyword("MUTEX");
13360            } else {
13361                self.write_keyword("STATUS");
13362            }
13363        }
13364
13365        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13366        if !s.privileges.is_empty() {
13367            self.write_space();
13368            self.write_keyword("WITH PRIVILEGES");
13369            self.write_space();
13370            for (i, priv_name) in s.privileges.iter().enumerate() {
13371                if i > 0 {
13372                    self.write(", ");
13373                }
13374                self.write_keyword(priv_name);
13375            }
13376        }
13377
13378        Ok(())
13379    }
13380
13381    // ==================== End DDL Generation ====================
13382
13383    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13384        use crate::dialects::DialectType;
13385        match lit {
13386            Literal::String(s) => {
13387                self.generate_string_literal(s)?;
13388            }
13389            Literal::Number(n) => {
13390                if matches!(self.config.dialect, Some(DialectType::MySQL))
13391                    && n.len() > 2
13392                    && (n.starts_with("0x") || n.starts_with("0X"))
13393                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13394                {
13395                    return self.generate_identifier(&Identifier {
13396                        name: n.clone(),
13397                        quoted: true,
13398                        trailing_comments: Vec::new(),
13399                        span: None,
13400                    });
13401                }
13402                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13403                // for dialects that don't support them (MySQL interprets as identifier).
13404                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13405                let n = if n.contains('_')
13406                    && !matches!(
13407                        self.config.dialect,
13408                        Some(DialectType::ClickHouse)
13409                            | Some(DialectType::DuckDB)
13410                            | Some(DialectType::PostgreSQL)
13411                            | Some(DialectType::Hive)
13412                            | Some(DialectType::Spark)
13413                            | Some(DialectType::Databricks)
13414                    ) {
13415                    std::borrow::Cow::Owned(n.replace('_', ""))
13416                } else {
13417                    std::borrow::Cow::Borrowed(n.as_str())
13418                };
13419                // Normalize numbers starting with decimal point to have leading zero
13420                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13421                if n.starts_with('.') {
13422                    self.write("0");
13423                    self.write(&n);
13424                } else if n.starts_with("-.") {
13425                    // Handle negative numbers like -.25 -> -0.25
13426                    self.write("-0");
13427                    self.write(&n[1..]);
13428                } else {
13429                    self.write(&n);
13430                }
13431            }
13432            Literal::HexString(h) => {
13433                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13434                match self.config.dialect {
13435                    Some(DialectType::Spark)
13436                    | Some(DialectType::Databricks)
13437                    | Some(DialectType::Teradata) => self.write("X'"),
13438                    _ => self.write("x'"),
13439                }
13440                self.write(h);
13441                self.write("'");
13442            }
13443            Literal::HexNumber(h) => {
13444                // Hex number (0xA) - integer in hex notation (from BigQuery)
13445                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13446                // For other dialects, convert to decimal integer
13447                match self.config.dialect {
13448                    Some(DialectType::BigQuery)
13449                    | Some(DialectType::TSQL)
13450                    | Some(DialectType::Fabric) => {
13451                        self.write("0x");
13452                        self.write(h);
13453                    }
13454                    _ => {
13455                        // Convert hex to decimal
13456                        if let Ok(val) = u64::from_str_radix(h, 16) {
13457                            self.write(&val.to_string());
13458                        } else {
13459                            // Fallback: keep as 0x notation
13460                            self.write("0x");
13461                            self.write(h);
13462                        }
13463                    }
13464                }
13465            }
13466            Literal::BitString(b) => {
13467                // Bit string B'0101...'
13468                self.write("B'");
13469                self.write(b);
13470                self.write("'");
13471            }
13472            Literal::ByteString(b) => {
13473                // Byte string b'...' (BigQuery style)
13474                self.write("b'");
13475                // Escape special characters for output
13476                self.write_escaped_byte_string(b);
13477                self.write("'");
13478            }
13479            Literal::NationalString(s) => {
13480                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13481                // Other dialects strip the N prefix and output as regular string
13482                let keep_n_prefix = matches!(
13483                    self.config.dialect,
13484                    Some(DialectType::TSQL)
13485                        | Some(DialectType::Oracle)
13486                        | Some(DialectType::MySQL)
13487                        | None
13488                );
13489                if keep_n_prefix {
13490                    self.write("N'");
13491                } else {
13492                    self.write("'");
13493                }
13494                self.write(s);
13495                self.write("'");
13496            }
13497            Literal::Date(d) => {
13498                self.generate_date_literal(d)?;
13499            }
13500            Literal::Time(t) => {
13501                self.generate_time_literal(t)?;
13502            }
13503            Literal::Timestamp(ts) => {
13504                self.generate_timestamp_literal(ts)?;
13505            }
13506            Literal::Datetime(dt) => {
13507                self.generate_datetime_literal(dt)?;
13508            }
13509            Literal::TripleQuotedString(s, _quote_char) => {
13510                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13511                if matches!(
13512                    self.config.dialect,
13513                    Some(crate::dialects::DialectType::BigQuery)
13514                        | Some(crate::dialects::DialectType::DuckDB)
13515                        | Some(crate::dialects::DialectType::Snowflake)
13516                        | Some(crate::dialects::DialectType::Spark)
13517                        | Some(crate::dialects::DialectType::Hive)
13518                        | Some(crate::dialects::DialectType::Presto)
13519                        | Some(crate::dialects::DialectType::Trino)
13520                        | Some(crate::dialects::DialectType::PostgreSQL)
13521                        | Some(crate::dialects::DialectType::MySQL)
13522                        | Some(crate::dialects::DialectType::Redshift)
13523                        | Some(crate::dialects::DialectType::TSQL)
13524                        | Some(crate::dialects::DialectType::Oracle)
13525                        | Some(crate::dialects::DialectType::ClickHouse)
13526                        | Some(crate::dialects::DialectType::Databricks)
13527                        | Some(crate::dialects::DialectType::SQLite)
13528                ) {
13529                    self.generate_string_literal(s)?;
13530                } else {
13531                    // Preserve triple-quoted string syntax for generic/unknown dialects
13532                    let quotes = format!("{0}{0}{0}", _quote_char);
13533                    self.write(&quotes);
13534                    self.write(s);
13535                    self.write(&quotes);
13536                }
13537            }
13538            Literal::EscapeString(s) => {
13539                // PostgreSQL escape string: e'...' or E'...'
13540                // Token text format is "e:content" or "E:content"
13541                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13542                use crate::dialects::DialectType;
13543                let content = if let Some(c) = s.strip_prefix("e:") {
13544                    c
13545                } else if let Some(c) = s.strip_prefix("E:") {
13546                    c
13547                } else {
13548                    s.as_str()
13549                };
13550
13551                // MySQL: output the content without quotes or prefix
13552                if matches!(
13553                    self.config.dialect,
13554                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13555                ) {
13556                    self.write(content);
13557                } else {
13558                    // Some dialects use lowercase e' prefix
13559                    let prefix = if matches!(
13560                        self.config.dialect,
13561                        Some(DialectType::SingleStore)
13562                            | Some(DialectType::DuckDB)
13563                            | Some(DialectType::PostgreSQL)
13564                            | Some(DialectType::CockroachDB)
13565                            | Some(DialectType::Materialize)
13566                            | Some(DialectType::RisingWave)
13567                    ) {
13568                        "e'"
13569                    } else {
13570                        "E'"
13571                    };
13572
13573                    // Normalize \' to '' for output
13574                    let normalized = content.replace("\\'", "''");
13575                    self.write(prefix);
13576                    self.write(&normalized);
13577                    self.write("'");
13578                }
13579            }
13580            Literal::DollarString(s) => {
13581                // Convert dollar-quoted strings to single-quoted strings
13582                // (like Python sqlglot's rawstring_sql)
13583                use crate::dialects::DialectType;
13584                // Extract content from tag\x00content format
13585                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13586                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13587                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13588                // Step 2: Determine quote escaping style
13589                // Snowflake: ' -> \' (backslash escape)
13590                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13591                let use_backslash_quote =
13592                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13593
13594                let mut escaped = String::with_capacity(content.len() + 4);
13595                for ch in content.chars() {
13596                    if escape_backslash && ch == '\\' {
13597                        // Escape backslash first (before quote escaping)
13598                        escaped.push('\\');
13599                        escaped.push('\\');
13600                    } else if ch == '\'' {
13601                        if use_backslash_quote {
13602                            escaped.push('\\');
13603                            escaped.push('\'');
13604                        } else {
13605                            escaped.push('\'');
13606                            escaped.push('\'');
13607                        }
13608                    } else {
13609                        escaped.push(ch);
13610                    }
13611                }
13612                self.write("'");
13613                self.write(&escaped);
13614                self.write("'");
13615            }
13616            Literal::RawString(s) => {
13617                // Raw strings (r"..." or r'...') contain literal backslashes.
13618                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13619                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13620                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13621                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13622                use crate::dialects::DialectType;
13623
13624                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13625                let escape_backslash = matches!(
13626                    self.config.dialect,
13627                    Some(DialectType::BigQuery)
13628                        | Some(DialectType::MySQL)
13629                        | Some(DialectType::SingleStore)
13630                        | Some(DialectType::TiDB)
13631                        | Some(DialectType::Hive)
13632                        | Some(DialectType::Spark)
13633                        | Some(DialectType::Databricks)
13634                        | Some(DialectType::Drill)
13635                        | Some(DialectType::Snowflake)
13636                        | Some(DialectType::Redshift)
13637                        | Some(DialectType::ClickHouse)
13638                );
13639
13640                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13641                // These escape quotes as \' instead of ''
13642                let backslash_escapes_quote = matches!(
13643                    self.config.dialect,
13644                    Some(DialectType::BigQuery)
13645                        | Some(DialectType::Hive)
13646                        | Some(DialectType::Spark)
13647                        | Some(DialectType::Databricks)
13648                        | Some(DialectType::Drill)
13649                        | Some(DialectType::Snowflake)
13650                        | Some(DialectType::Redshift)
13651                );
13652
13653                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13654                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13655                let supports_escape_sequences = escape_backslash;
13656
13657                let mut escaped = String::with_capacity(s.len() + 4);
13658                for ch in s.chars() {
13659                    if escape_backslash && ch == '\\' {
13660                        // Double the backslash for the target dialect
13661                        escaped.push('\\');
13662                        escaped.push('\\');
13663                    } else if ch == '\'' {
13664                        if backslash_escapes_quote {
13665                            // Use backslash to escape the quote: \'
13666                            escaped.push('\\');
13667                            escaped.push('\'');
13668                        } else {
13669                            // Use SQL standard quote doubling: ''
13670                            escaped.push('\'');
13671                            escaped.push('\'');
13672                        }
13673                    } else if supports_escape_sequences {
13674                        // Apply ESCAPED_SEQUENCES mapping for special chars
13675                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13676                        match ch {
13677                            '\n' => {
13678                                escaped.push('\\');
13679                                escaped.push('n');
13680                            }
13681                            '\r' => {
13682                                escaped.push('\\');
13683                                escaped.push('r');
13684                            }
13685                            '\t' => {
13686                                escaped.push('\\');
13687                                escaped.push('t');
13688                            }
13689                            '\x07' => {
13690                                escaped.push('\\');
13691                                escaped.push('a');
13692                            }
13693                            '\x08' => {
13694                                escaped.push('\\');
13695                                escaped.push('b');
13696                            }
13697                            '\x0C' => {
13698                                escaped.push('\\');
13699                                escaped.push('f');
13700                            }
13701                            '\x0B' => {
13702                                escaped.push('\\');
13703                                escaped.push('v');
13704                            }
13705                            _ => escaped.push(ch),
13706                        }
13707                    } else {
13708                        escaped.push(ch);
13709                    }
13710                }
13711                self.write("'");
13712                self.write(&escaped);
13713                self.write("'");
13714            }
13715        }
13716        Ok(())
13717    }
13718
13719    /// Generate a DATE literal with dialect-specific formatting
13720    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13721        use crate::dialects::DialectType;
13722
13723        match self.config.dialect {
13724            // SQL Server uses CONVERT or CAST
13725            Some(DialectType::TSQL) => {
13726                self.write("CAST('");
13727                self.write(d);
13728                self.write("' AS DATE)");
13729            }
13730            // BigQuery uses CAST syntax for type literals
13731            // DATE 'value' -> CAST('value' AS DATE)
13732            Some(DialectType::BigQuery) => {
13733                self.write("CAST('");
13734                self.write(d);
13735                self.write("' AS DATE)");
13736            }
13737            // Exasol uses CAST syntax for DATE literals
13738            // DATE 'value' -> CAST('value' AS DATE)
13739            Some(DialectType::Exasol) => {
13740                self.write("CAST('");
13741                self.write(d);
13742                self.write("' AS DATE)");
13743            }
13744            // Snowflake uses CAST syntax for DATE literals
13745            // DATE 'value' -> CAST('value' AS DATE)
13746            Some(DialectType::Snowflake) => {
13747                self.write("CAST('");
13748                self.write(d);
13749                self.write("' AS DATE)");
13750            }
13751            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13752            Some(DialectType::PostgreSQL)
13753            | Some(DialectType::MySQL)
13754            | Some(DialectType::SingleStore)
13755            | Some(DialectType::TiDB)
13756            | Some(DialectType::Redshift) => {
13757                self.write("CAST('");
13758                self.write(d);
13759                self.write("' AS DATE)");
13760            }
13761            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13762            Some(DialectType::DuckDB)
13763            | Some(DialectType::Presto)
13764            | Some(DialectType::Trino)
13765            | Some(DialectType::Athena)
13766            | Some(DialectType::Spark)
13767            | Some(DialectType::Databricks)
13768            | Some(DialectType::Hive) => {
13769                self.write("CAST('");
13770                self.write(d);
13771                self.write("' AS DATE)");
13772            }
13773            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13774            Some(DialectType::Oracle) => {
13775                self.write("TO_DATE('");
13776                self.write(d);
13777                self.write("', 'YYYY-MM-DD')");
13778            }
13779            // Standard SQL: DATE '...'
13780            _ => {
13781                self.write_keyword("DATE");
13782                self.write(" '");
13783                self.write(d);
13784                self.write("'");
13785            }
13786        }
13787        Ok(())
13788    }
13789
13790    /// Generate a TIME literal with dialect-specific formatting
13791    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13792        use crate::dialects::DialectType;
13793
13794        match self.config.dialect {
13795            // SQL Server uses CONVERT or CAST
13796            Some(DialectType::TSQL) => {
13797                self.write("CAST('");
13798                self.write(t);
13799                self.write("' AS TIME)");
13800            }
13801            // Standard SQL: TIME '...'
13802            _ => {
13803                self.write_keyword("TIME");
13804                self.write(" '");
13805                self.write(t);
13806                self.write("'");
13807            }
13808        }
13809        Ok(())
13810    }
13811
13812    /// Generate a date expression for Dremio, converting DATE literals to CAST
13813    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13814        use crate::expressions::Literal;
13815
13816        match expr {
13817            Expression::Literal(Literal::Date(d)) => {
13818                // DATE 'value' -> CAST('value' AS DATE)
13819                self.write("CAST('");
13820                self.write(d);
13821                self.write("' AS DATE)");
13822            }
13823            _ => {
13824                // For all other expressions, generate normally
13825                self.generate_expression(expr)?;
13826            }
13827        }
13828        Ok(())
13829    }
13830
13831    /// Generate a TIMESTAMP literal with dialect-specific formatting
13832    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13833        use crate::dialects::DialectType;
13834
13835        match self.config.dialect {
13836            // SQL Server uses CONVERT or CAST
13837            Some(DialectType::TSQL) => {
13838                self.write("CAST('");
13839                self.write(ts);
13840                self.write("' AS DATETIME2)");
13841            }
13842            // BigQuery uses CAST syntax for type literals
13843            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13844            Some(DialectType::BigQuery) => {
13845                self.write("CAST('");
13846                self.write(ts);
13847                self.write("' AS TIMESTAMP)");
13848            }
13849            // Snowflake uses CAST syntax for TIMESTAMP literals
13850            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13851            Some(DialectType::Snowflake) => {
13852                self.write("CAST('");
13853                self.write(ts);
13854                self.write("' AS TIMESTAMP)");
13855            }
13856            // Dremio uses CAST syntax for TIMESTAMP literals
13857            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13858            Some(DialectType::Dremio) => {
13859                self.write("CAST('");
13860                self.write(ts);
13861                self.write("' AS TIMESTAMP)");
13862            }
13863            // Exasol uses CAST syntax for TIMESTAMP literals
13864            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13865            Some(DialectType::Exasol) => {
13866                self.write("CAST('");
13867                self.write(ts);
13868                self.write("' AS TIMESTAMP)");
13869            }
13870            // Oracle prefers TO_TIMESTAMP function call
13871            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13872            Some(DialectType::Oracle) => {
13873                self.write("TO_TIMESTAMP('");
13874                self.write(ts);
13875                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13876            }
13877            // Presto/Trino: always use CAST for TIMESTAMP literals
13878            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13879                if Self::timestamp_has_timezone(ts) {
13880                    self.write("CAST('");
13881                    self.write(ts);
13882                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13883                } else {
13884                    self.write("CAST('");
13885                    self.write(ts);
13886                    self.write("' AS TIMESTAMP)");
13887                }
13888            }
13889            // ClickHouse: CAST('...' AS Nullable(DateTime))
13890            Some(DialectType::ClickHouse) => {
13891                self.write("CAST('");
13892                self.write(ts);
13893                self.write("' AS Nullable(DateTime))");
13894            }
13895            // Spark: CAST('...' AS TIMESTAMP)
13896            Some(DialectType::Spark) => {
13897                self.write("CAST('");
13898                self.write(ts);
13899                self.write("' AS TIMESTAMP)");
13900            }
13901            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13902            // but TIMESTAMP '...' for special values like 'epoch'
13903            Some(DialectType::Redshift) => {
13904                if ts == "epoch" {
13905                    self.write_keyword("TIMESTAMP");
13906                    self.write(" '");
13907                    self.write(ts);
13908                    self.write("'");
13909                } else {
13910                    self.write("CAST('");
13911                    self.write(ts);
13912                    self.write("' AS TIMESTAMP)");
13913                }
13914            }
13915            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13916            Some(DialectType::PostgreSQL)
13917            | Some(DialectType::Hive)
13918            | Some(DialectType::SQLite)
13919            | Some(DialectType::DuckDB)
13920            | Some(DialectType::Athena)
13921            | Some(DialectType::Drill)
13922            | Some(DialectType::Teradata) => {
13923                self.write("CAST('");
13924                self.write(ts);
13925                self.write("' AS TIMESTAMP)");
13926            }
13927            // MySQL/StarRocks: CAST('...' AS DATETIME)
13928            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13929                self.write("CAST('");
13930                self.write(ts);
13931                self.write("' AS DATETIME)");
13932            }
13933            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13934            Some(DialectType::Databricks) => {
13935                self.write("CAST('");
13936                self.write(ts);
13937                self.write("' AS TIMESTAMP_NTZ)");
13938            }
13939            // Standard SQL: TIMESTAMP '...'
13940            _ => {
13941                self.write_keyword("TIMESTAMP");
13942                self.write(" '");
13943                self.write(ts);
13944                self.write("'");
13945            }
13946        }
13947        Ok(())
13948    }
13949
13950    /// Check if a timestamp string contains a timezone identifier
13951    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13952    fn timestamp_has_timezone(ts: &str) -> bool {
13953        // Check for common IANA timezone patterns: Continent/City format
13954        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13955        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13956        let ts_lower = ts.to_lowercase();
13957
13958        // Check for Continent/City pattern (most common)
13959        let continent_prefixes = [
13960            "africa/",
13961            "america/",
13962            "antarctica/",
13963            "arctic/",
13964            "asia/",
13965            "atlantic/",
13966            "australia/",
13967            "europe/",
13968            "indian/",
13969            "pacific/",
13970            "etc/",
13971            "brazil/",
13972            "canada/",
13973            "chile/",
13974            "mexico/",
13975            "us/",
13976        ];
13977
13978        for prefix in &continent_prefixes {
13979            if ts_lower.contains(prefix) {
13980                return true;
13981            }
13982        }
13983
13984        // Check for standalone timezone abbreviations at the end
13985        // These typically appear after the time portion
13986        let tz_abbrevs = [
13987            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13988            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13989            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13990        ];
13991
13992        for abbrev in &tz_abbrevs {
13993            if ts_lower.ends_with(abbrev) {
13994                return true;
13995            }
13996        }
13997
13998        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13999        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14000        // Look for pattern: space followed by + or - and digits (optionally with :)
14001        let trimmed = ts.trim();
14002        if let Some(last_space) = trimmed.rfind(' ') {
14003            let suffix = &trimmed[last_space + 1..];
14004            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14005                // Check if rest is numeric (possibly with : for hh:mm format)
14006                let rest = &suffix[1..];
14007                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14008                    return true;
14009                }
14010            }
14011        }
14012
14013        false
14014    }
14015
14016    /// Generate a DATETIME literal with dialect-specific formatting
14017    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14018        use crate::dialects::DialectType;
14019
14020        match self.config.dialect {
14021            // BigQuery uses CAST syntax for type literals
14022            // DATETIME 'value' -> CAST('value' AS DATETIME)
14023            Some(DialectType::BigQuery) => {
14024                self.write("CAST('");
14025                self.write(dt);
14026                self.write("' AS DATETIME)");
14027            }
14028            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14029            Some(DialectType::DuckDB) => {
14030                self.write("CAST('");
14031                self.write(dt);
14032                self.write("' AS TIMESTAMP)");
14033            }
14034            // DATETIME is primarily a BigQuery type
14035            // Output as DATETIME '...' for dialects that support it
14036            _ => {
14037                self.write_keyword("DATETIME");
14038                self.write(" '");
14039                self.write(dt);
14040                self.write("'");
14041            }
14042        }
14043        Ok(())
14044    }
14045
14046    /// Generate a string literal with dialect-specific escaping
14047    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14048        use crate::dialects::DialectType;
14049
14050        match self.config.dialect {
14051            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14052            // and backslash escaping for special characters like newlines
14053            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14054            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14055                // Hive/Spark use backslash escaping for quotes (\') and special chars
14056                self.write("'");
14057                for c in s.chars() {
14058                    match c {
14059                        '\'' => self.write("\\'"),
14060                        '\\' => self.write("\\\\"),
14061                        '\n' => self.write("\\n"),
14062                        '\r' => self.write("\\r"),
14063                        '\t' => self.write("\\t"),
14064                        '\0' => self.write("\\0"),
14065                        _ => self.output.push(c),
14066                    }
14067                }
14068                self.write("'");
14069            }
14070            Some(DialectType::Drill) => {
14071                // Drill uses SQL-standard quote doubling ('') for quotes,
14072                // but backslash escaping for special characters
14073                self.write("'");
14074                for c in s.chars() {
14075                    match c {
14076                        '\'' => self.write("''"),
14077                        '\\' => self.write("\\\\"),
14078                        '\n' => self.write("\\n"),
14079                        '\r' => self.write("\\r"),
14080                        '\t' => self.write("\\t"),
14081                        '\0' => self.write("\\0"),
14082                        _ => self.output.push(c),
14083                    }
14084                }
14085                self.write("'");
14086            }
14087            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14088                self.write("'");
14089                for c in s.chars() {
14090                    match c {
14091                        // MySQL uses SQL standard quote doubling
14092                        '\'' => self.write("''"),
14093                        '\\' => self.write("\\\\"),
14094                        '\n' => self.write("\\n"),
14095                        '\r' => self.write("\\r"),
14096                        '\t' => self.write("\\t"),
14097                        // sqlglot writes a literal NUL for this case
14098                        '\0' => self.output.push('\0'),
14099                        _ => self.output.push(c),
14100                    }
14101                }
14102                self.write("'");
14103            }
14104            // BigQuery: Uses backslash escaping
14105            Some(DialectType::BigQuery) => {
14106                self.write("'");
14107                for c in s.chars() {
14108                    match c {
14109                        '\'' => self.write("\\'"),
14110                        '\\' => self.write("\\\\"),
14111                        '\n' => self.write("\\n"),
14112                        '\r' => self.write("\\r"),
14113                        '\t' => self.write("\\t"),
14114                        '\0' => self.write("\\0"),
14115                        '\x07' => self.write("\\a"),
14116                        '\x08' => self.write("\\b"),
14117                        '\x0C' => self.write("\\f"),
14118                        '\x0B' => self.write("\\v"),
14119                        _ => self.output.push(c),
14120                    }
14121                }
14122                self.write("'");
14123            }
14124            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14125            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14126            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14127            Some(DialectType::Athena) => {
14128                if self.athena_hive_context {
14129                    // Hive-style: backslash escaping
14130                    self.write("'");
14131                    for c in s.chars() {
14132                        match c {
14133                            '\'' => self.write("\\'"),
14134                            '\\' => self.write("\\\\"),
14135                            '\n' => self.write("\\n"),
14136                            '\r' => self.write("\\r"),
14137                            '\t' => self.write("\\t"),
14138                            '\0' => self.write("\\0"),
14139                            _ => self.output.push(c),
14140                        }
14141                    }
14142                    self.write("'");
14143                } else {
14144                    // Trino-style: SQL-standard escaping, preserve backslashes
14145                    self.write("'");
14146                    for c in s.chars() {
14147                        match c {
14148                            '\'' => self.write("''"),
14149                            // Preserve backslashes literally (no re-escaping)
14150                            _ => self.output.push(c),
14151                        }
14152                    }
14153                    self.write("'");
14154                }
14155            }
14156            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14157            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14158            // becomes string value '\\'), so we should NOT re-escape backslashes.
14159            // We only need to escape single quotes.
14160            Some(DialectType::Snowflake) => {
14161                self.write("'");
14162                for c in s.chars() {
14163                    match c {
14164                        '\'' => self.write("\\'"),
14165                        // Backslashes are already escaped in the tokenized string, don't re-escape
14166                        // Only escape special characters that might not have been escaped
14167                        '\n' => self.write("\\n"),
14168                        '\r' => self.write("\\r"),
14169                        '\t' => self.write("\\t"),
14170                        _ => self.output.push(c),
14171                    }
14172                }
14173                self.write("'");
14174            }
14175            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14176            Some(DialectType::PostgreSQL) => {
14177                self.write("'");
14178                for c in s.chars() {
14179                    match c {
14180                        '\'' => self.write("''"),
14181                        _ => self.output.push(c),
14182                    }
14183                }
14184                self.write("'");
14185            }
14186            // Redshift: Uses backslash escaping for single quotes
14187            Some(DialectType::Redshift) => {
14188                self.write("'");
14189                for c in s.chars() {
14190                    match c {
14191                        '\'' => self.write("\\'"),
14192                        _ => self.output.push(c),
14193                    }
14194                }
14195                self.write("'");
14196            }
14197            // Oracle: Uses standard double single-quote escaping
14198            Some(DialectType::Oracle) => {
14199                self.write("'");
14200                self.write(&s.replace('\'', "''"));
14201                self.write("'");
14202            }
14203            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14204            // backslash escaping for backslashes and special characters
14205            Some(DialectType::ClickHouse) => {
14206                self.write("'");
14207                for c in s.chars() {
14208                    match c {
14209                        '\'' => self.write("''"),
14210                        '\\' => self.write("\\\\"),
14211                        '\n' => self.write("\\n"),
14212                        '\r' => self.write("\\r"),
14213                        '\t' => self.write("\\t"),
14214                        '\0' => self.write("\\0"),
14215                        '\x07' => self.write("\\a"),
14216                        '\x08' => self.write("\\b"),
14217                        '\x0C' => self.write("\\f"),
14218                        '\x0B' => self.write("\\v"),
14219                        // Non-printable characters: emit as \xNN hex escapes
14220                        c if c.is_control() || (c as u32) < 0x20 => {
14221                            let byte = c as u32;
14222                            if byte < 256 {
14223                                self.write(&format!("\\x{:02X}", byte));
14224                            } else {
14225                                self.output.push(c);
14226                            }
14227                        }
14228                        _ => self.output.push(c),
14229                    }
14230                }
14231                self.write("'");
14232            }
14233            // Default: SQL standard double single quotes (works for most dialects)
14234            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14235            _ => {
14236                self.write("'");
14237                self.write(&s.replace('\'', "''"));
14238                self.write("'");
14239            }
14240        }
14241        Ok(())
14242    }
14243
14244    /// Write a byte string with proper escaping for BigQuery-style byte literals
14245    /// Escapes characters as \xNN hex escapes where needed
14246    fn write_escaped_byte_string(&mut self, s: &str) {
14247        for c in s.chars() {
14248            match c {
14249                // Escape single quotes
14250                '\'' => self.write("\\'"),
14251                // Escape backslashes
14252                '\\' => self.write("\\\\"),
14253                // Keep all printable characters (including non-ASCII) as-is
14254                _ if !c.is_control() => self.output.push(c),
14255                // Escape control characters as hex
14256                _ => {
14257                    let byte = c as u32;
14258                    if byte < 256 {
14259                        self.write(&format!("\\x{:02x}", byte));
14260                    } else {
14261                        // For unicode characters, write each UTF-8 byte
14262                        for b in c.to_string().as_bytes() {
14263                            self.write(&format!("\\x{:02x}", b));
14264                        }
14265                    }
14266                }
14267            }
14268        }
14269    }
14270
14271    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14272        use crate::dialects::DialectType;
14273
14274        // Different dialects have different boolean literal formats
14275        match self.config.dialect {
14276            // SQL Server typically uses 1/0 for boolean literals in many contexts
14277            // However, TRUE/FALSE also works in modern versions
14278            Some(DialectType::TSQL) => {
14279                self.write(if b.value { "1" } else { "0" });
14280            }
14281            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14282            Some(DialectType::Oracle) => {
14283                self.write(if b.value { "1" } else { "0" });
14284            }
14285            // MySQL accepts TRUE/FALSE as aliases for 1/0
14286            Some(DialectType::MySQL) => {
14287                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14288            }
14289            // Most other dialects support TRUE/FALSE
14290            _ => {
14291                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14292            }
14293        }
14294        Ok(())
14295    }
14296
14297    /// Generate an identifier that's used as an alias name
14298    /// This quotes reserved keywords in addition to already-quoted identifiers
14299    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14300        let name = &id.name;
14301        let quote_style = &self.config.identifier_quote_style;
14302
14303        // For aliases, quote if:
14304        // 1. The identifier was explicitly quoted in the source
14305        // 2. The identifier is a reserved keyword for the current dialect
14306        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14307
14308        // Normalize identifier if configured
14309        let output_name = if self.config.normalize_identifiers && !id.quoted {
14310            name.to_lowercase()
14311        } else {
14312            name.to_string()
14313        };
14314
14315        if needs_quoting {
14316            // Escape any quote characters within the identifier
14317            let escaped_name = if quote_style.start == quote_style.end {
14318                output_name.replace(
14319                    quote_style.end,
14320                    &format!("{}{}", quote_style.end, quote_style.end),
14321                )
14322            } else {
14323                output_name.replace(
14324                    quote_style.end,
14325                    &format!("{}{}", quote_style.end, quote_style.end),
14326                )
14327            };
14328            self.write(&format!(
14329                "{}{}{}",
14330                quote_style.start, escaped_name, quote_style.end
14331            ));
14332        } else {
14333            self.write(&output_name);
14334        }
14335
14336        // Output trailing comments
14337        for comment in &id.trailing_comments {
14338            self.write(" ");
14339            self.write_formatted_comment(comment);
14340        }
14341        Ok(())
14342    }
14343
14344    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14345        use crate::dialects::DialectType;
14346
14347        let name = &id.name;
14348
14349        // For Athena, use backticks in Hive context, double quotes in Trino context
14350        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14351            && self.athena_hive_context
14352        {
14353            &IdentifierQuoteStyle::BACKTICK
14354        } else {
14355            &self.config.identifier_quote_style
14356        };
14357
14358        // Quote if:
14359        // 1. The identifier was explicitly quoted in the source
14360        // 2. The identifier is a reserved keyword for the current dialect
14361        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14362        // This matches Python sqlglot's identifier_sql behavior
14363        // Also quote identifiers starting with digits if the target dialect doesn't support them
14364        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14365        let needs_digit_quoting = starts_with_digit
14366            && !self.config.identifiers_can_start_with_digit
14367            && self.config.dialect.is_some();
14368        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14369            && name.len() > 2
14370            && (name.starts_with("0x") || name.starts_with("0X"))
14371            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14372        let needs_quoting = id.quoted
14373            || self.is_reserved_keyword(name)
14374            || self.config.always_quote_identifiers
14375            || needs_digit_quoting
14376            || mysql_invalid_hex_identifier;
14377
14378        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14379        // When quoted, we need to output `name`(16) not `name(16)`
14380        let (base_name, suffix) = if needs_quoting {
14381            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14382            if let Some(paren_pos) = name.find('(') {
14383                let base = &name[..paren_pos];
14384                let rest = &name[paren_pos..];
14385                // Verify it looks like (digits) or (digits) ASC/DESC
14386                if rest.starts_with('(')
14387                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14388                {
14389                    // Check if content between parens is all digits
14390                    let close_paren = rest.find(')').unwrap_or(rest.len());
14391                    let inside = &rest[1..close_paren];
14392                    if inside.chars().all(|c| c.is_ascii_digit()) {
14393                        (base.to_string(), rest.to_string())
14394                    } else {
14395                        (name.to_string(), String::new())
14396                    }
14397                } else {
14398                    (name.to_string(), String::new())
14399                }
14400            } else if name.ends_with(" ASC") {
14401                let base = &name[..name.len() - 4];
14402                (base.to_string(), " ASC".to_string())
14403            } else if name.ends_with(" DESC") {
14404                let base = &name[..name.len() - 5];
14405                (base.to_string(), " DESC".to_string())
14406            } else {
14407                (name.to_string(), String::new())
14408            }
14409        } else {
14410            (name.to_string(), String::new())
14411        };
14412
14413        // Normalize identifier if configured, with special handling for Exasol
14414        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14415        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14416        let output_name = if self.config.normalize_identifiers && !id.quoted {
14417            base_name.to_lowercase()
14418        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14419            && !id.quoted
14420            && self.is_reserved_keyword(name)
14421        {
14422            // Exasol: uppercase reserved keywords when quoting them
14423            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14424            base_name.to_uppercase()
14425        } else {
14426            base_name
14427        };
14428
14429        if needs_quoting {
14430            // Escape any quote characters within the identifier
14431            let escaped_name = if quote_style.start == quote_style.end {
14432                // Same start/end char (e.g., " or `) - double the quote char
14433                output_name.replace(
14434                    quote_style.end,
14435                    &format!("{}{}", quote_style.end, quote_style.end),
14436                )
14437            } else {
14438                // Different start/end (e.g., [ and ]) - escape only the end char
14439                output_name.replace(
14440                    quote_style.end,
14441                    &format!("{}{}", quote_style.end, quote_style.end),
14442                )
14443            };
14444            self.write(&format!(
14445                "{}{}{}{}",
14446                quote_style.start, escaped_name, quote_style.end, suffix
14447            ));
14448        } else {
14449            self.write(&output_name);
14450        }
14451
14452        // Output trailing comments
14453        for comment in &id.trailing_comments {
14454            self.write(" ");
14455            self.write_formatted_comment(comment);
14456        }
14457        Ok(())
14458    }
14459
14460    fn generate_column(&mut self, col: &Column) -> Result<()> {
14461        use crate::dialects::DialectType;
14462
14463        if let Some(table) = &col.table {
14464            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14465            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14466            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14467            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14468                && !table.quoted
14469                && table.name.eq_ignore_ascii_case("LOCAL");
14470
14471            if is_exasol_local_prefix {
14472                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14473                self.write("LOCAL");
14474            } else {
14475                self.generate_identifier(table)?;
14476            }
14477            self.write(".");
14478        }
14479        self.generate_identifier(&col.name)?;
14480        // Oracle-style join marker (+)
14481        // Only output if dialect supports it (Oracle, Exasol)
14482        if col.join_mark && self.config.supports_column_join_marks {
14483            self.write(" (+)");
14484        }
14485        // Output trailing comments
14486        for comment in &col.trailing_comments {
14487            self.write_space();
14488            self.write_formatted_comment(comment);
14489        }
14490        Ok(())
14491    }
14492
14493    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14494    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14495    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14496        use crate::dialects::DialectType;
14497        use crate::expressions::PseudocolumnType;
14498
14499        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14500        if pc.kind == PseudocolumnType::Sysdate
14501            && !matches!(
14502                self.config.dialect,
14503                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14504            )
14505        {
14506            self.write_keyword("CURRENT_TIMESTAMP");
14507            // Add () for dialects that expect it
14508            if matches!(
14509                self.config.dialect,
14510                Some(DialectType::MySQL)
14511                    | Some(DialectType::ClickHouse)
14512                    | Some(DialectType::Spark)
14513                    | Some(DialectType::Databricks)
14514                    | Some(DialectType::Hive)
14515            ) {
14516                self.write("()");
14517            }
14518        } else {
14519            self.write(pc.kind.as_str());
14520        }
14521        Ok(())
14522    }
14523
14524    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14525    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14526        use crate::dialects::DialectType;
14527
14528        // Generate native CONNECT BY for Oracle and Snowflake
14529        // For other dialects, add a comment noting manual conversion needed
14530        let supports_connect_by = matches!(
14531            self.config.dialect,
14532            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14533        );
14534
14535        if !supports_connect_by && self.config.dialect.is_some() {
14536            // Add comment for unsupported dialects
14537            if self.config.pretty {
14538                self.write_newline();
14539            } else {
14540                self.write_space();
14541            }
14542            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14543        }
14544
14545        // Generate START WITH if present (before CONNECT BY)
14546        if let Some(start) = &connect.start {
14547            if self.config.pretty {
14548                self.write_newline();
14549            } else {
14550                self.write_space();
14551            }
14552            self.write_keyword("START WITH");
14553            self.write_space();
14554            self.generate_expression(start)?;
14555        }
14556
14557        // Generate CONNECT BY
14558        if self.config.pretty {
14559            self.write_newline();
14560        } else {
14561            self.write_space();
14562        }
14563        self.write_keyword("CONNECT BY");
14564        if connect.nocycle {
14565            self.write_space();
14566            self.write_keyword("NOCYCLE");
14567        }
14568        self.write_space();
14569        self.generate_expression(&connect.connect)?;
14570
14571        Ok(())
14572    }
14573
14574    /// Generate Connect expression (for Expression::Connect variant)
14575    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14576        self.generate_connect(connect)
14577    }
14578
14579    /// Generate PRIOR expression
14580    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14581        self.write_keyword("PRIOR");
14582        self.write_space();
14583        self.generate_expression(&prior.this)?;
14584        Ok(())
14585    }
14586
14587    /// Generate CONNECT_BY_ROOT function
14588    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14589    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14590        self.write_keyword("CONNECT_BY_ROOT");
14591        self.write_space();
14592        self.generate_expression(&cbr.this)?;
14593        Ok(())
14594    }
14595
14596    /// Generate MATCH_RECOGNIZE clause
14597    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14598        use crate::dialects::DialectType;
14599
14600        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14601        let supports_match_recognize = matches!(
14602            self.config.dialect,
14603            Some(DialectType::Oracle)
14604                | Some(DialectType::Snowflake)
14605                | Some(DialectType::Presto)
14606                | Some(DialectType::Trino)
14607        );
14608
14609        // Generate the source table first
14610        if let Some(source) = &mr.this {
14611            self.generate_expression(source)?;
14612        }
14613
14614        if !supports_match_recognize {
14615            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14616            return Ok(());
14617        }
14618
14619        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14620        if self.config.pretty {
14621            self.write_newline();
14622        } else {
14623            self.write_space();
14624        }
14625
14626        self.write_keyword("MATCH_RECOGNIZE");
14627        self.write(" (");
14628
14629        if self.config.pretty {
14630            self.indent_level += 1;
14631        }
14632
14633        let mut needs_separator = false;
14634
14635        // PARTITION BY
14636        if let Some(partition_by) = &mr.partition_by {
14637            if !partition_by.is_empty() {
14638                if self.config.pretty {
14639                    self.write_newline();
14640                    self.write_indent();
14641                }
14642                self.write_keyword("PARTITION BY");
14643                self.write_space();
14644                for (i, expr) in partition_by.iter().enumerate() {
14645                    if i > 0 {
14646                        self.write(", ");
14647                    }
14648                    self.generate_expression(expr)?;
14649                }
14650                needs_separator = true;
14651            }
14652        }
14653
14654        // ORDER BY
14655        if let Some(order_by) = &mr.order_by {
14656            if !order_by.is_empty() {
14657                if needs_separator {
14658                    if self.config.pretty {
14659                        self.write_newline();
14660                        self.write_indent();
14661                    } else {
14662                        self.write_space();
14663                    }
14664                } else if self.config.pretty {
14665                    self.write_newline();
14666                    self.write_indent();
14667                }
14668                self.write_keyword("ORDER BY");
14669                // In pretty mode, put each ORDER BY column on a new indented line
14670                if self.config.pretty {
14671                    self.indent_level += 1;
14672                    for (i, ordered) in order_by.iter().enumerate() {
14673                        if i > 0 {
14674                            self.write(",");
14675                        }
14676                        self.write_newline();
14677                        self.write_indent();
14678                        self.generate_ordered(ordered)?;
14679                    }
14680                    self.indent_level -= 1;
14681                } else {
14682                    self.write_space();
14683                    for (i, ordered) in order_by.iter().enumerate() {
14684                        if i > 0 {
14685                            self.write(", ");
14686                        }
14687                        self.generate_ordered(ordered)?;
14688                    }
14689                }
14690                needs_separator = true;
14691            }
14692        }
14693
14694        // MEASURES
14695        if let Some(measures) = &mr.measures {
14696            if !measures.is_empty() {
14697                if needs_separator {
14698                    if self.config.pretty {
14699                        self.write_newline();
14700                        self.write_indent();
14701                    } else {
14702                        self.write_space();
14703                    }
14704                } else if self.config.pretty {
14705                    self.write_newline();
14706                    self.write_indent();
14707                }
14708                self.write_keyword("MEASURES");
14709                // In pretty mode, put each MEASURE on a new indented line
14710                if self.config.pretty {
14711                    self.indent_level += 1;
14712                    for (i, measure) in measures.iter().enumerate() {
14713                        if i > 0 {
14714                            self.write(",");
14715                        }
14716                        self.write_newline();
14717                        self.write_indent();
14718                        // Handle RUNNING/FINAL prefix
14719                        if let Some(semantics) = &measure.window_frame {
14720                            match semantics {
14721                                MatchRecognizeSemantics::Running => {
14722                                    self.write_keyword("RUNNING");
14723                                    self.write_space();
14724                                }
14725                                MatchRecognizeSemantics::Final => {
14726                                    self.write_keyword("FINAL");
14727                                    self.write_space();
14728                                }
14729                            }
14730                        }
14731                        self.generate_expression(&measure.this)?;
14732                    }
14733                    self.indent_level -= 1;
14734                } else {
14735                    self.write_space();
14736                    for (i, measure) in measures.iter().enumerate() {
14737                        if i > 0 {
14738                            self.write(", ");
14739                        }
14740                        // Handle RUNNING/FINAL prefix
14741                        if let Some(semantics) = &measure.window_frame {
14742                            match semantics {
14743                                MatchRecognizeSemantics::Running => {
14744                                    self.write_keyword("RUNNING");
14745                                    self.write_space();
14746                                }
14747                                MatchRecognizeSemantics::Final => {
14748                                    self.write_keyword("FINAL");
14749                                    self.write_space();
14750                                }
14751                            }
14752                        }
14753                        self.generate_expression(&measure.this)?;
14754                    }
14755                }
14756                needs_separator = true;
14757            }
14758        }
14759
14760        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14761        if let Some(rows) = &mr.rows {
14762            if needs_separator {
14763                if self.config.pretty {
14764                    self.write_newline();
14765                    self.write_indent();
14766                } else {
14767                    self.write_space();
14768                }
14769            } else if self.config.pretty {
14770                self.write_newline();
14771                self.write_indent();
14772            }
14773            match rows {
14774                MatchRecognizeRows::OneRowPerMatch => {
14775                    self.write_keyword("ONE ROW PER MATCH");
14776                }
14777                MatchRecognizeRows::AllRowsPerMatch => {
14778                    self.write_keyword("ALL ROWS PER MATCH");
14779                }
14780                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14781                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14782                }
14783                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14784                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14785                }
14786                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14787                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14788                }
14789            }
14790            needs_separator = true;
14791        }
14792
14793        // AFTER MATCH SKIP
14794        if let Some(after) = &mr.after {
14795            if needs_separator {
14796                if self.config.pretty {
14797                    self.write_newline();
14798                    self.write_indent();
14799                } else {
14800                    self.write_space();
14801                }
14802            } else if self.config.pretty {
14803                self.write_newline();
14804                self.write_indent();
14805            }
14806            match after {
14807                MatchRecognizeAfter::PastLastRow => {
14808                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14809                }
14810                MatchRecognizeAfter::ToNextRow => {
14811                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14812                }
14813                MatchRecognizeAfter::ToFirst(ident) => {
14814                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14815                    self.write_space();
14816                    self.generate_identifier(ident)?;
14817                }
14818                MatchRecognizeAfter::ToLast(ident) => {
14819                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14820                    self.write_space();
14821                    self.generate_identifier(ident)?;
14822                }
14823            }
14824            needs_separator = true;
14825        }
14826
14827        // PATTERN
14828        if let Some(pattern) = &mr.pattern {
14829            if needs_separator {
14830                if self.config.pretty {
14831                    self.write_newline();
14832                    self.write_indent();
14833                } else {
14834                    self.write_space();
14835                }
14836            } else if self.config.pretty {
14837                self.write_newline();
14838                self.write_indent();
14839            }
14840            self.write_keyword("PATTERN");
14841            self.write_space();
14842            self.write("(");
14843            self.write(pattern);
14844            self.write(")");
14845            needs_separator = true;
14846        }
14847
14848        // DEFINE
14849        if let Some(define) = &mr.define {
14850            if !define.is_empty() {
14851                if needs_separator {
14852                    if self.config.pretty {
14853                        self.write_newline();
14854                        self.write_indent();
14855                    } else {
14856                        self.write_space();
14857                    }
14858                } else if self.config.pretty {
14859                    self.write_newline();
14860                    self.write_indent();
14861                }
14862                self.write_keyword("DEFINE");
14863                // In pretty mode, put each DEFINE on a new indented line
14864                if self.config.pretty {
14865                    self.indent_level += 1;
14866                    for (i, (name, expr)) in define.iter().enumerate() {
14867                        if i > 0 {
14868                            self.write(",");
14869                        }
14870                        self.write_newline();
14871                        self.write_indent();
14872                        self.generate_identifier(name)?;
14873                        self.write(" AS ");
14874                        self.generate_expression(expr)?;
14875                    }
14876                    self.indent_level -= 1;
14877                } else {
14878                    self.write_space();
14879                    for (i, (name, expr)) in define.iter().enumerate() {
14880                        if i > 0 {
14881                            self.write(", ");
14882                        }
14883                        self.generate_identifier(name)?;
14884                        self.write(" AS ");
14885                        self.generate_expression(expr)?;
14886                    }
14887                }
14888            }
14889        }
14890
14891        if self.config.pretty {
14892            self.indent_level -= 1;
14893            self.write_newline();
14894        }
14895        self.write(")");
14896
14897        // Alias - only include AS if it was explicitly present in the input
14898        if let Some(alias) = &mr.alias {
14899            self.write(" ");
14900            if mr.alias_explicit_as {
14901                self.write_keyword("AS");
14902                self.write(" ");
14903            }
14904            self.generate_identifier(alias)?;
14905        }
14906
14907        Ok(())
14908    }
14909
14910    /// Generate a query hint /*+ ... */
14911    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14912        use crate::dialects::DialectType;
14913
14914        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14915        let supports_hints = matches!(
14916            self.config.dialect,
14917            None |  // No dialect = preserve everything
14918            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14919            Some(DialectType::Spark) | Some(DialectType::Hive) |
14920            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14921        );
14922
14923        if !supports_hints || hint.expressions.is_empty() {
14924            return Ok(());
14925        }
14926
14927        // First, expand raw hint text into individual hint strings
14928        // This handles the case where the parser stored multiple hints as a single raw string
14929        let mut hint_strings: Vec<String> = Vec::new();
14930        for expr in &hint.expressions {
14931            match expr {
14932                HintExpression::Raw(text) => {
14933                    // Parse raw hint text into individual hint function calls
14934                    let parsed = self.parse_raw_hint_text(text);
14935                    hint_strings.extend(parsed);
14936                }
14937                _ => {
14938                    hint_strings.push(self.hint_expression_to_string(expr)?);
14939                }
14940            }
14941        }
14942
14943        // In pretty mode with multiple hints, always use multiline format
14944        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14945        // always joins with newlines in pretty mode
14946        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14947
14948        if use_multiline {
14949            // Pretty print with each hint on its own line
14950            self.write(" /*+ ");
14951            for (i, hint_str) in hint_strings.iter().enumerate() {
14952                if i > 0 {
14953                    self.write_newline();
14954                    self.write("  "); // 2-space indent within hint block
14955                }
14956                self.write(hint_str);
14957            }
14958            self.write(" */");
14959        } else {
14960            // Single line format
14961            self.write(" /*+ ");
14962            let sep = match self.config.dialect {
14963                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14964                _ => " ",
14965            };
14966            for (i, hint_str) in hint_strings.iter().enumerate() {
14967                if i > 0 {
14968                    self.write(sep);
14969                }
14970                self.write(hint_str);
14971            }
14972            self.write(" */");
14973        }
14974
14975        Ok(())
14976    }
14977
14978    /// Parse raw hint text into individual hint function calls
14979    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14980    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14981    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14982        let mut results = Vec::new();
14983        let mut chars = text.chars().peekable();
14984        let mut current = String::new();
14985        let mut paren_depth = 0;
14986        let mut has_unparseable_content = false;
14987        let mut position_after_last_function = 0;
14988        let mut char_position = 0;
14989
14990        while let Some(c) = chars.next() {
14991            char_position += c.len_utf8();
14992            match c {
14993                '(' => {
14994                    paren_depth += 1;
14995                    current.push(c);
14996                }
14997                ')' => {
14998                    paren_depth -= 1;
14999                    current.push(c);
15000                    // When we close the outer parenthesis, we've completed a hint function
15001                    if paren_depth == 0 {
15002                        let trimmed = current.trim().to_string();
15003                        if !trimmed.is_empty() {
15004                            // Format this hint for pretty printing if needed
15005                            let formatted = self.format_hint_function(&trimmed);
15006                            results.push(formatted);
15007                        }
15008                        current.clear();
15009                        position_after_last_function = char_position;
15010                    }
15011                }
15012                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15013                    // Space/comma/whitespace outside parentheses - skip
15014                }
15015                _ if paren_depth == 0 => {
15016                    // Character outside parentheses - accumulate for potential hint name
15017                    current.push(c);
15018                }
15019                _ => {
15020                    current.push(c);
15021                }
15022            }
15023        }
15024
15025        // Check if there's remaining text after the last function call
15026        let remaining_text = text[position_after_last_function..].trim();
15027        if !remaining_text.is_empty() {
15028            // Check if it looks like valid hint function names
15029            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15030            // If we see multiple words without parens, it's likely unparseable
15031            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15032            let looks_like_hint_functions = words.iter().all(|word| {
15033                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15034                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15035            });
15036
15037            if !looks_like_hint_functions && words.len() > 1 {
15038                has_unparseable_content = true;
15039            }
15040        }
15041
15042        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15043        if has_unparseable_content {
15044            return vec![text.trim().to_string()];
15045        }
15046
15047        // If we couldn't parse anything, return the original text as a single hint
15048        if results.is_empty() {
15049            results.push(text.trim().to_string());
15050        }
15051
15052        results
15053    }
15054
15055    /// Format a hint function for pretty printing
15056    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15057    fn format_hint_function(&self, hint: &str) -> String {
15058        if !self.config.pretty {
15059            return hint.to_string();
15060        }
15061
15062        // Try to parse NAME(args) pattern
15063        if let Some(paren_pos) = hint.find('(') {
15064            if hint.ends_with(')') {
15065                let name = &hint[..paren_pos];
15066                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15067
15068                // Parse arguments (space-separated for Oracle hints)
15069                let args: Vec<&str> = args_str.split_whitespace().collect();
15070
15071                // Calculate total width of arguments
15072                let total_args_width: usize =
15073                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15074
15075                // If too wide, format on multiple lines
15076                if total_args_width > self.config.max_text_width && !args.is_empty() {
15077                    let mut result = format!("{}(\n", name);
15078                    for arg in &args {
15079                        result.push_str("    "); // 4-space indent for args
15080                        result.push_str(arg);
15081                        result.push('\n');
15082                    }
15083                    result.push_str("  )"); // 2-space indent for closing paren
15084                    return result;
15085                }
15086            }
15087        }
15088
15089        hint.to_string()
15090    }
15091
15092    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15093    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15094        match expr {
15095            HintExpression::Function { name, args } => {
15096                // Generate each argument to a string
15097                let arg_strings: Vec<String> = args
15098                    .iter()
15099                    .map(|arg| {
15100                        let mut gen = Generator::with_config(self.config.clone());
15101                        gen.generate_expression(arg)?;
15102                        Ok(gen.output)
15103                    })
15104                    .collect::<Result<Vec<_>>>()?;
15105
15106                // Oracle hints use space-separated arguments, not comma-separated
15107                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15108                    + arg_strings.len().saturating_sub(1); // spaces between args
15109
15110                // Check if function args need multiline formatting
15111                // Use too_wide check for argument formatting
15112                let args_multiline =
15113                    self.config.pretty && total_args_width > self.config.max_text_width;
15114
15115                if args_multiline && !arg_strings.is_empty() {
15116                    // Multiline format for long argument lists
15117                    let mut result = format!("{}(\n", name);
15118                    for arg_str in &arg_strings {
15119                        result.push_str("    "); // 4-space indent for args
15120                        result.push_str(arg_str);
15121                        result.push('\n');
15122                    }
15123                    result.push_str("  )"); // 2-space indent for closing paren
15124                    Ok(result)
15125                } else {
15126                    // Single line format with space-separated args (Oracle style)
15127                    let args_str = arg_strings.join(" ");
15128                    Ok(format!("{}({})", name, args_str))
15129                }
15130            }
15131            HintExpression::Identifier(name) => Ok(name.clone()),
15132            HintExpression::Raw(text) => {
15133                // For pretty printing, try to format the raw text
15134                if self.config.pretty {
15135                    Ok(self.format_hint_function(text))
15136                } else {
15137                    Ok(text.clone())
15138                }
15139            }
15140        }
15141    }
15142
15143    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15144        // PostgreSQL ONLY modifier: prevents scanning child tables
15145        if table.only {
15146            self.write_keyword("ONLY");
15147            self.write_space();
15148        }
15149
15150        // Check for Snowflake IDENTIFIER() function
15151        if let Some(ref identifier_func) = table.identifier_func {
15152            self.generate_expression(identifier_func)?;
15153        } else {
15154            if let Some(catalog) = &table.catalog {
15155                self.generate_identifier(catalog)?;
15156                self.write(".");
15157            }
15158            if let Some(schema) = &table.schema {
15159                self.generate_identifier(schema)?;
15160                self.write(".");
15161            }
15162            self.generate_identifier(&table.name)?;
15163        }
15164
15165        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15166        if let Some(changes) = &table.changes {
15167            self.write(" ");
15168            self.generate_changes(changes)?;
15169        }
15170
15171        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15172        if !table.partitions.is_empty() {
15173            self.write_space();
15174            self.write_keyword("PARTITION");
15175            self.write("(");
15176            for (i, partition) in table.partitions.iter().enumerate() {
15177                if i > 0 {
15178                    self.write(", ");
15179                }
15180                self.generate_identifier(partition)?;
15181            }
15182            self.write(")");
15183        }
15184
15185        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15186        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15187        if table.changes.is_none() {
15188            if let Some(when) = &table.when {
15189                self.write_space();
15190                self.generate_historical_data(when)?;
15191            }
15192        }
15193
15194        // Output TSQL FOR SYSTEM_TIME temporal clause
15195        if let Some(ref system_time) = table.system_time {
15196            self.write_space();
15197            self.write(system_time);
15198        }
15199
15200        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15201        if let Some(ref version) = table.version {
15202            self.write_space();
15203            self.generate_version(version)?;
15204        }
15205
15206        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15207        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15208        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15209        let alias_post_tablesample = self.config.alias_post_tablesample;
15210
15211        if alias_post_tablesample {
15212            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15213            self.generate_table_sample_clause(table)?;
15214        }
15215
15216        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15217        // For SQLite, INDEXED BY hints come after the alias, so skip here
15218        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15219            && table.hints.iter().any(|h| {
15220                if let Expression::Identifier(id) = h {
15221                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15222                } else {
15223                    false
15224                }
15225            });
15226        if !table.hints.is_empty() && !is_sqlite_hint {
15227            for hint in &table.hints {
15228                self.write_space();
15229                self.generate_expression(hint)?;
15230            }
15231        }
15232
15233        if let Some(alias) = &table.alias {
15234            self.write_space();
15235            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15236            // Generic mode and most dialects always use AS for table aliases
15237            let always_use_as = self.config.dialect.is_none()
15238                || matches!(
15239                    self.config.dialect,
15240                    Some(DialectType::Generic)
15241                        | Some(DialectType::PostgreSQL)
15242                        | Some(DialectType::Redshift)
15243                        | Some(DialectType::Snowflake)
15244                        | Some(DialectType::BigQuery)
15245                        | Some(DialectType::Presto)
15246                        | Some(DialectType::Trino)
15247                        | Some(DialectType::TSQL)
15248                        | Some(DialectType::Fabric)
15249                        | Some(DialectType::MySQL)
15250                        | Some(DialectType::Spark)
15251                        | Some(DialectType::Hive)
15252                        | Some(DialectType::SQLite)
15253                        | Some(DialectType::Drill)
15254                );
15255            let is_stage_ref = table.name.name.starts_with('@');
15256            // Oracle never uses AS for table aliases
15257            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15258            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15259                self.write_keyword("AS");
15260                self.write_space();
15261            }
15262            self.generate_identifier(alias)?;
15263
15264            // Output column aliases if present: AS t(c1, c2)
15265            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15266            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15267                self.write("(");
15268                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15269                    if i > 0 {
15270                        self.write(", ");
15271                    }
15272                    self.generate_identifier(col_alias)?;
15273                }
15274                self.write(")");
15275            }
15276        }
15277
15278        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15279        if !alias_post_tablesample {
15280            self.generate_table_sample_clause(table)?;
15281        }
15282
15283        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15284        if is_sqlite_hint {
15285            for hint in &table.hints {
15286                self.write_space();
15287                self.generate_expression(hint)?;
15288            }
15289        }
15290
15291        // ClickHouse FINAL modifier
15292        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15293            self.write_space();
15294            self.write_keyword("FINAL");
15295        }
15296
15297        // Output trailing comments
15298        for comment in &table.trailing_comments {
15299            self.write_space();
15300            self.write_formatted_comment(comment);
15301        }
15302
15303        Ok(())
15304    }
15305
15306    /// Helper to output TABLESAMPLE clause for a table reference
15307    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15308        if let Some(ref ts) = table.table_sample {
15309            self.write_space();
15310            if ts.is_using_sample {
15311                self.write_keyword("USING SAMPLE");
15312            } else {
15313                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15314                self.write_keyword(self.config.tablesample_keywords);
15315            }
15316            self.generate_sample_body(ts)?;
15317            // Seed for table-level sample - use dialect's configured keyword
15318            if let Some(ref seed) = ts.seed {
15319                self.write_space();
15320                self.write_keyword(self.config.tablesample_seed_keyword);
15321                self.write(" (");
15322                self.generate_expression(seed)?;
15323                self.write(")");
15324            }
15325        }
15326        Ok(())
15327    }
15328
15329    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15330        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15331        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15332
15333        if sr.quoted {
15334            self.write("'");
15335        }
15336
15337        self.write(&sr.name);
15338        if let Some(path) = &sr.path {
15339            self.write(path);
15340        }
15341
15342        if sr.quoted {
15343            self.write("'");
15344        }
15345
15346        // Output FILE_FORMAT and PATTERN if present
15347        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15348        if has_options {
15349            self.write(" (");
15350            let mut first = true;
15351
15352            if let Some(file_format) = &sr.file_format {
15353                if !first {
15354                    self.write(", ");
15355                }
15356                self.write_keyword("FILE_FORMAT");
15357                self.write(" => ");
15358                self.generate_expression(file_format)?;
15359                first = false;
15360            }
15361
15362            if let Some(pattern) = &sr.pattern {
15363                if !first {
15364                    self.write(", ");
15365                }
15366                self.write_keyword("PATTERN");
15367                self.write(" => '");
15368                self.write(pattern);
15369                self.write("'");
15370            }
15371
15372            self.write(")");
15373        }
15374        Ok(())
15375    }
15376
15377    fn generate_star(&mut self, star: &Star) -> Result<()> {
15378        use crate::dialects::DialectType;
15379
15380        if let Some(table) = &star.table {
15381            self.generate_identifier(table)?;
15382            self.write(".");
15383        }
15384        self.write("*");
15385
15386        // Generate EXCLUDE/EXCEPT clause based on dialect
15387        if let Some(except) = &star.except {
15388            if !except.is_empty() {
15389                self.write_space();
15390                // Use dialect-appropriate keyword
15391                match self.config.dialect {
15392                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15393                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15394                        self.write_keyword("EXCLUDE")
15395                    }
15396                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15397                }
15398                self.write(" (");
15399                for (i, col) in except.iter().enumerate() {
15400                    if i > 0 {
15401                        self.write(", ");
15402                    }
15403                    self.generate_identifier(col)?;
15404                }
15405                self.write(")");
15406            }
15407        }
15408
15409        // Generate REPLACE clause
15410        if let Some(replace) = &star.replace {
15411            if !replace.is_empty() {
15412                self.write_space();
15413                self.write_keyword("REPLACE");
15414                self.write(" (");
15415                for (i, alias) in replace.iter().enumerate() {
15416                    if i > 0 {
15417                        self.write(", ");
15418                    }
15419                    self.generate_expression(&alias.this)?;
15420                    self.write_space();
15421                    self.write_keyword("AS");
15422                    self.write_space();
15423                    self.generate_identifier(&alias.alias)?;
15424                }
15425                self.write(")");
15426            }
15427        }
15428
15429        // Generate RENAME clause (Snowflake specific)
15430        if let Some(rename) = &star.rename {
15431            if !rename.is_empty() {
15432                self.write_space();
15433                self.write_keyword("RENAME");
15434                self.write(" (");
15435                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15436                    if i > 0 {
15437                        self.write(", ");
15438                    }
15439                    self.generate_identifier(old_name)?;
15440                    self.write_space();
15441                    self.write_keyword("AS");
15442                    self.write_space();
15443                    self.generate_identifier(new_name)?;
15444                }
15445                self.write(")");
15446            }
15447        }
15448
15449        // Output trailing comments
15450        for comment in &star.trailing_comments {
15451            self.write_space();
15452            self.write_formatted_comment(comment);
15453        }
15454
15455        Ok(())
15456    }
15457
15458    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15459    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15460        self.write("{");
15461        match expr {
15462            Expression::Star(star) => {
15463                // Generate the star (table.* or just * with optional EXCLUDE)
15464                self.generate_star(star)?;
15465            }
15466            Expression::ILike(ilike) => {
15467                // {* ILIKE 'pattern'} syntax
15468                self.generate_expression(&ilike.left)?;
15469                self.write_space();
15470                self.write_keyword("ILIKE");
15471                self.write_space();
15472                self.generate_expression(&ilike.right)?;
15473            }
15474            _ => {
15475                self.generate_expression(expr)?;
15476            }
15477        }
15478        self.write("}");
15479        Ok(())
15480    }
15481
15482    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15483        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15484        // to avoid duplication (comments are captured as both Column.trailing_comments
15485        // and Alias.pre_alias_comments during parsing)
15486        match &alias.this {
15487            Expression::Column(col) => {
15488                // Generate column without trailing comments - they're in pre_alias_comments
15489                if let Some(table) = &col.table {
15490                    self.generate_identifier(table)?;
15491                    self.write(".");
15492                }
15493                self.generate_identifier(&col.name)?;
15494            }
15495            _ => {
15496                self.generate_expression(&alias.this)?;
15497            }
15498        }
15499
15500        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15501        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15502        // keep pre-alias comments in their original position (between expression and AS).
15503        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15504            for comment in &alias.pre_alias_comments {
15505                self.write_space();
15506                self.write_formatted_comment(comment);
15507            }
15508        }
15509
15510        use crate::dialects::DialectType;
15511
15512        // Determine if we should skip AS keyword for table-valued function aliases
15513        // Oracle and some other dialects don't use AS for table aliases
15514        // Note: We specifically use TableFromRows here, NOT Function, because Function
15515        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15516        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15517        let is_table_source = matches!(
15518            &alias.this,
15519            Expression::JSONTable(_)
15520                | Expression::XMLTable(_)
15521                | Expression::TableFromRows(_)
15522                | Expression::Unnest(_)
15523                | Expression::MatchRecognize(_)
15524                | Expression::Select(_)
15525                | Expression::Subquery(_)
15526                | Expression::Paren(_)
15527        );
15528        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15529        let skip_as = is_table_source && dialect_skips_table_alias_as;
15530
15531        self.write_space();
15532        if !skip_as {
15533            self.write_keyword("AS");
15534            self.write_space();
15535        }
15536
15537        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15538        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15539
15540        // Check if we have column aliases only (no table alias name)
15541        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15542            // Generate AS (col1, col2, ...)
15543            self.write("(");
15544            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15545                if i > 0 {
15546                    self.write(", ");
15547                }
15548                self.generate_alias_identifier(col_alias)?;
15549            }
15550            self.write(")");
15551        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15552            // Generate AS alias(col1, col2, ...)
15553            self.generate_alias_identifier(&alias.alias)?;
15554            self.write("(");
15555            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15556                if i > 0 {
15557                    self.write(", ");
15558                }
15559                self.generate_alias_identifier(col_alias)?;
15560            }
15561            self.write(")");
15562        } else {
15563            // Simple alias (or BigQuery without column aliases)
15564            self.generate_alias_identifier(&alias.alias)?;
15565        }
15566
15567        // Output trailing comments (comments after the alias)
15568        for comment in &alias.trailing_comments {
15569            self.write_space();
15570            self.write_formatted_comment(comment);
15571        }
15572
15573        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15574        // moves pre-alias comments to after the alias. When there are trailing_comments,
15575        // the pre-alias comments were already lost (consumed as column trailing comments
15576        // that were then used as pre_alias_comments). We always emit them after alias.
15577        if alias.trailing_comments.is_empty() {
15578            for comment in &alias.pre_alias_comments {
15579                self.write_space();
15580                self.write_formatted_comment(comment);
15581            }
15582        }
15583
15584        Ok(())
15585    }
15586
15587    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15588        use crate::dialects::DialectType;
15589
15590        // SingleStore uses :> syntax
15591        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15592            self.generate_expression(&cast.this)?;
15593            self.write(" :> ");
15594            self.generate_data_type(&cast.to)?;
15595            return Ok(());
15596        }
15597
15598        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15599        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15600            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15601                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15602            if is_unknown_type {
15603                if let Some(format) = &cast.format {
15604                    self.write_keyword("CAST");
15605                    self.write("(");
15606                    self.generate_expression(&cast.this)?;
15607                    self.write_space();
15608                    self.write_keyword("AS");
15609                    self.write_space();
15610                    self.write_keyword("FORMAT");
15611                    self.write_space();
15612                    self.generate_expression(format)?;
15613                    self.write(")");
15614                    return Ok(());
15615                }
15616            }
15617        }
15618
15619        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15620        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15621        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15622            if let Some(format) = &cast.format {
15623                // Check if target type is DATE or TIMESTAMP
15624                let is_date = matches!(cast.to, DataType::Date);
15625                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15626
15627                if is_date || is_timestamp {
15628                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15629                    self.write_keyword(func_name);
15630                    self.write("(");
15631                    self.generate_expression(&cast.this)?;
15632                    self.write(", ");
15633
15634                    // Normalize format string for Oracle (HH -> HH12)
15635                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15636                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15637                        let normalized = self.normalize_oracle_format(fmt_str);
15638                        self.write("'");
15639                        self.write(&normalized);
15640                        self.write("'");
15641                    } else {
15642                        self.generate_expression(format)?;
15643                    }
15644
15645                    self.write(")");
15646                    return Ok(());
15647                }
15648            }
15649        }
15650
15651        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15652        // This preserves sqlglot's typed inline array literal output.
15653        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15654            if let Expression::Array(arr) = &cast.this {
15655                self.generate_data_type(&cast.to)?;
15656                // Output just the bracket content [values] without the ARRAY prefix
15657                self.write("[");
15658                for (i, expr) in arr.expressions.iter().enumerate() {
15659                    if i > 0 {
15660                        self.write(", ");
15661                    }
15662                    self.generate_expression(expr)?;
15663                }
15664                self.write("]");
15665                return Ok(());
15666            }
15667            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15668                self.generate_data_type(&cast.to)?;
15669                self.generate_expression(&cast.this)?;
15670                return Ok(());
15671            }
15672        }
15673
15674        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15675        // convert the inner Struct to ROW(values...) format
15676        if matches!(
15677            self.config.dialect,
15678            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15679        ) {
15680            if let Expression::Struct(ref s) = cast.this {
15681                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15682                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15683                    self.write_keyword("CAST");
15684                    self.write("(");
15685                    self.generate_struct_as_row(s)?;
15686                    self.write_space();
15687                    self.write_keyword("AS");
15688                    self.write_space();
15689                    self.generate_data_type(&cast.to)?;
15690                    self.write(")");
15691                    return Ok(());
15692                }
15693            }
15694        }
15695
15696        // Determine if we should use :: syntax based on dialect
15697        // PostgreSQL prefers :: for identity, most others prefer CAST()
15698        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15699
15700        if use_double_colon {
15701            // PostgreSQL :: syntax: expr::type
15702            self.generate_expression(&cast.this)?;
15703            self.write("::");
15704            self.generate_data_type(&cast.to)?;
15705        } else {
15706            // Standard CAST() syntax
15707            self.write_keyword("CAST");
15708            self.write("(");
15709            self.generate_expression(&cast.this)?;
15710            self.write_space();
15711            self.write_keyword("AS");
15712            self.write_space();
15713            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15714            // This matches Python sqlglot's CAST_MAPPING behavior
15715            if matches!(
15716                self.config.dialect,
15717                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15718            ) {
15719                match &cast.to {
15720                    DataType::Custom { ref name } => {
15721                        let upper = name.to_uppercase();
15722                        match upper.as_str() {
15723                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15724                            | "TINYBLOB" => {
15725                                self.write_keyword("CHAR");
15726                            }
15727                            _ => {
15728                                self.generate_data_type(&cast.to)?;
15729                            }
15730                        }
15731                    }
15732                    DataType::VarChar { length, .. } => {
15733                        // MySQL CAST: VARCHAR -> CHAR
15734                        self.write_keyword("CHAR");
15735                        if let Some(n) = length {
15736                            self.write(&format!("({})", n));
15737                        }
15738                    }
15739                    DataType::Text => {
15740                        // MySQL CAST: TEXT -> CHAR
15741                        self.write_keyword("CHAR");
15742                    }
15743                    DataType::Timestamp {
15744                        precision,
15745                        timezone: false,
15746                    } => {
15747                        // MySQL CAST: TIMESTAMP -> DATETIME
15748                        self.write_keyword("DATETIME");
15749                        if let Some(p) = precision {
15750                            self.write(&format!("({})", p));
15751                        }
15752                    }
15753                    _ => {
15754                        self.generate_data_type(&cast.to)?;
15755                    }
15756                }
15757            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15758                // Snowflake CAST: STRING -> VARCHAR
15759                match &cast.to {
15760                    DataType::String { length } => {
15761                        self.write_keyword("VARCHAR");
15762                        if let Some(n) = length {
15763                            self.write(&format!("({})", n));
15764                        }
15765                    }
15766                    _ => {
15767                        self.generate_data_type(&cast.to)?;
15768                    }
15769                }
15770            } else {
15771                self.generate_data_type(&cast.to)?;
15772            }
15773
15774            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15775            if let Some(default) = &cast.default {
15776                self.write_space();
15777                self.write_keyword("DEFAULT");
15778                self.write_space();
15779                self.generate_expression(default)?;
15780                self.write_space();
15781                self.write_keyword("ON");
15782                self.write_space();
15783                self.write_keyword("CONVERSION");
15784                self.write_space();
15785                self.write_keyword("ERROR");
15786            }
15787
15788            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15789            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15790            if let Some(format) = &cast.format {
15791                // Check if Oracle dialect - use comma syntax
15792                if matches!(
15793                    self.config.dialect,
15794                    Some(crate::dialects::DialectType::Oracle)
15795                ) {
15796                    self.write(", ");
15797                } else {
15798                    self.write_space();
15799                    self.write_keyword("FORMAT");
15800                    self.write_space();
15801                }
15802                self.generate_expression(format)?;
15803            }
15804
15805            self.write(")");
15806            // Output trailing comments
15807            for comment in &cast.trailing_comments {
15808                self.write_space();
15809                self.write_formatted_comment(comment);
15810            }
15811        }
15812        Ok(())
15813    }
15814
15815    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15816    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15817    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15818        self.write_keyword("ROW");
15819        self.write("(");
15820        for (i, (_, expr)) in s.fields.iter().enumerate() {
15821            if i > 0 {
15822                self.write(", ");
15823            }
15824            // Recursively convert inner Struct to ROW format
15825            if let Expression::Struct(ref inner_s) = expr {
15826                self.generate_struct_as_row(inner_s)?;
15827            } else {
15828                self.generate_expression(expr)?;
15829            }
15830        }
15831        self.write(")");
15832        Ok(())
15833    }
15834
15835    /// Normalize Oracle date/time format strings
15836    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15837    fn normalize_oracle_format(&self, format: &str) -> String {
15838        // Replace standalone HH with HH12 (but not HH12 or HH24)
15839        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15840        let mut result = String::new();
15841        let chars: Vec<char> = format.chars().collect();
15842        let mut i = 0;
15843
15844        while i < chars.len() {
15845            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15846                // Check what follows HH
15847                if i + 2 < chars.len() {
15848                    let next = chars[i + 2];
15849                    if next == '1' || next == '2' {
15850                        // This is HH12 or HH24, keep as is
15851                        result.push('H');
15852                        result.push('H');
15853                        i += 2;
15854                        continue;
15855                    }
15856                }
15857                // Standalone HH -> HH12
15858                result.push_str("HH12");
15859                i += 2;
15860            } else {
15861                result.push(chars[i]);
15862                i += 1;
15863            }
15864        }
15865
15866        result
15867    }
15868
15869    /// Check if the current dialect prefers :: cast syntax
15870    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15871    /// So we return false for all dialects to match Python sqlglot's behavior
15872    fn dialect_prefers_double_colon(&self) -> bool {
15873        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15874        // Even PostgreSQL outputs CAST() not ::
15875        false
15876    }
15877
15878    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15879    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15880        use crate::dialects::DialectType;
15881
15882        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15883        let use_percent_operator = matches!(
15884            self.config.dialect,
15885            Some(DialectType::Snowflake)
15886                | Some(DialectType::MySQL)
15887                | Some(DialectType::Presto)
15888                | Some(DialectType::Trino)
15889                | Some(DialectType::PostgreSQL)
15890                | Some(DialectType::DuckDB)
15891                | Some(DialectType::Hive)
15892                | Some(DialectType::Spark)
15893                | Some(DialectType::Databricks)
15894                | Some(DialectType::Athena)
15895        );
15896
15897        if use_percent_operator {
15898            // Wrap complex expressions in parens to preserve precedence
15899            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15900            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15901            if needs_paren(&f.this) {
15902                self.write("(");
15903                self.generate_expression(&f.this)?;
15904                self.write(")");
15905            } else {
15906                self.generate_expression(&f.this)?;
15907            }
15908            self.write(" % ");
15909            if needs_paren(&f.expression) {
15910                self.write("(");
15911                self.generate_expression(&f.expression)?;
15912                self.write(")");
15913            } else {
15914                self.generate_expression(&f.expression)?;
15915            }
15916            Ok(())
15917        } else {
15918            self.generate_binary_func("MOD", &f.this, &f.expression)
15919        }
15920    }
15921
15922    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15923    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15924        use crate::dialects::DialectType;
15925
15926        // Snowflake normalizes IFNULL to COALESCE
15927        let func_name = match self.config.dialect {
15928            Some(DialectType::Snowflake) => "COALESCE",
15929            _ => "IFNULL",
15930        };
15931
15932        self.generate_binary_func(func_name, &f.this, &f.expression)
15933    }
15934
15935    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15936    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15937        // Use original function name if preserved (for identity tests)
15938        if let Some(ref original_name) = f.original_name {
15939            return self.generate_binary_func(original_name, &f.this, &f.expression);
15940        }
15941
15942        // Otherwise, use dialect-specific function names
15943        use crate::dialects::DialectType;
15944        let func_name = match self.config.dialect {
15945            Some(DialectType::Snowflake)
15946            | Some(DialectType::ClickHouse)
15947            | Some(DialectType::PostgreSQL)
15948            | Some(DialectType::Presto)
15949            | Some(DialectType::Trino)
15950            | Some(DialectType::Athena)
15951            | Some(DialectType::DuckDB)
15952            | Some(DialectType::BigQuery)
15953            | Some(DialectType::Spark)
15954            | Some(DialectType::Databricks)
15955            | Some(DialectType::Hive) => "COALESCE",
15956            Some(DialectType::MySQL)
15957            | Some(DialectType::Doris)
15958            | Some(DialectType::StarRocks)
15959            | Some(DialectType::SingleStore)
15960            | Some(DialectType::TiDB) => "IFNULL",
15961            _ => "NVL",
15962        };
15963
15964        self.generate_binary_func(func_name, &f.this, &f.expression)
15965    }
15966
15967    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15968    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15969        use crate::dialects::DialectType;
15970
15971        // Snowflake normalizes STDDEV_SAMP to STDDEV
15972        let func_name = match self.config.dialect {
15973            Some(DialectType::Snowflake) => "STDDEV",
15974            _ => "STDDEV_SAMP",
15975        };
15976
15977        self.generate_agg_func(func_name, f)
15978    }
15979
15980    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15981        self.generate_expression(&coll.this)?;
15982        self.write_space();
15983        self.write_keyword("COLLATE");
15984        self.write_space();
15985        if coll.quoted {
15986            // Single-quoted string: COLLATE 'de_DE'
15987            self.write("'");
15988            self.write(&coll.collation);
15989            self.write("'");
15990        } else if coll.double_quoted {
15991            // Double-quoted identifier: COLLATE "de_DE"
15992            self.write("\"");
15993            self.write(&coll.collation);
15994            self.write("\"");
15995        } else {
15996            // Unquoted identifier: COLLATE de_DE
15997            self.write(&coll.collation);
15998        }
15999        Ok(())
16000    }
16001
16002    fn generate_case(&mut self, case: &Case) -> Result<()> {
16003        // In pretty mode, decide whether to expand based on total text width
16004        let multiline_case = if self.config.pretty {
16005            // Build the flat representation to check width
16006            let mut statements: Vec<String> = Vec::new();
16007            let operand_str = if let Some(operand) = &case.operand {
16008                let s = self.generate_to_string(operand)?;
16009                statements.push(format!("CASE {}", s));
16010                s
16011            } else {
16012                statements.push("CASE".to_string());
16013                String::new()
16014            };
16015            let _ = operand_str;
16016            for (condition, result) in &case.whens {
16017                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16018                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16019            }
16020            if let Some(else_) = &case.else_ {
16021                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16022            }
16023            statements.push("END".to_string());
16024            self.too_wide(&statements)
16025        } else {
16026            false
16027        };
16028
16029        self.write_keyword("CASE");
16030        if let Some(operand) = &case.operand {
16031            self.write_space();
16032            self.generate_expression(operand)?;
16033        }
16034        if multiline_case {
16035            self.indent_level += 1;
16036        }
16037        for (condition, result) in &case.whens {
16038            if multiline_case {
16039                self.write_newline();
16040                self.write_indent();
16041            } else {
16042                self.write_space();
16043            }
16044            self.write_keyword("WHEN");
16045            self.write_space();
16046            self.generate_expression(condition)?;
16047            if multiline_case {
16048                self.write_newline();
16049                self.write_indent();
16050            } else {
16051                self.write_space();
16052            }
16053            self.write_keyword("THEN");
16054            self.write_space();
16055            self.generate_expression(result)?;
16056        }
16057        if let Some(else_) = &case.else_ {
16058            if multiline_case {
16059                self.write_newline();
16060                self.write_indent();
16061            } else {
16062                self.write_space();
16063            }
16064            self.write_keyword("ELSE");
16065            self.write_space();
16066            self.generate_expression(else_)?;
16067        }
16068        if multiline_case {
16069            self.indent_level -= 1;
16070            self.write_newline();
16071            self.write_indent();
16072        } else {
16073            self.write_space();
16074        }
16075        self.write_keyword("END");
16076        // Emit any comments that were attached to the CASE keyword
16077        for comment in &case.comments {
16078            self.write(" ");
16079            self.write_formatted_comment(comment);
16080        }
16081        Ok(())
16082    }
16083
16084    fn generate_function(&mut self, func: &Function) -> Result<()> {
16085        // Normalize function name based on dialect settings
16086        let normalized_name = self.normalize_func_name(&func.name);
16087        let upper_name = func.name.to_uppercase();
16088
16089        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16090        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16091            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16092        {
16093            self.write("LIST_FILTER(");
16094            self.write("[");
16095            for (i, arg) in func.args.iter().enumerate() {
16096                if i > 0 {
16097                    self.write(", ");
16098                }
16099                self.generate_expression(arg)?;
16100            }
16101            self.write("], _u -> NOT _u IS NULL)");
16102            return Ok(());
16103        }
16104
16105        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16106        if upper_name == "STRUCT"
16107            && !matches!(
16108                self.config.dialect,
16109                Some(DialectType::BigQuery)
16110                    | Some(DialectType::Spark)
16111                    | Some(DialectType::Databricks)
16112                    | Some(DialectType::Hive)
16113                    | None
16114            )
16115        {
16116            return self.generate_struct_function_cross_dialect(func);
16117        }
16118
16119        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16120        // This is an internal marker function for ::? JSON path syntax
16121        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16122            self.generate_expression(&func.args[0])?;
16123            self.write("::?");
16124            // Extract the key from the string literal
16125            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16126                self.write(key);
16127            } else {
16128                self.generate_expression(&func.args[1])?;
16129            }
16130            return Ok(());
16131        }
16132
16133        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16134        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16135            self.generate_expression(&func.args[0])?;
16136            self.write(" # ");
16137            self.generate_expression(&func.args[1])?;
16138            return Ok(());
16139        }
16140
16141        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16142        if matches!(
16143            self.config.dialect,
16144            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16145        ) && upper_name == "TRY"
16146            && func.args.len() == 1
16147        {
16148            self.generate_expression(&func.args[0])?;
16149            return Ok(());
16150        }
16151
16152        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16153        if self.config.dialect == Some(DialectType::ClickHouse)
16154            && upper_name == "TOSTARTOFDAY"
16155            && func.args.len() == 1
16156        {
16157            self.write("dateTrunc('DAY', ");
16158            self.generate_expression(&func.args[0])?;
16159            self.write(")");
16160            return Ok(());
16161        }
16162
16163        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16164        if self.config.dialect == Some(DialectType::Redshift)
16165            && upper_name == "CONCAT"
16166            && func.args.len() >= 2
16167        {
16168            for (i, arg) in func.args.iter().enumerate() {
16169                if i > 0 {
16170                    self.write(" || ");
16171                }
16172                self.generate_expression(arg)?;
16173            }
16174            return Ok(());
16175        }
16176
16177        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16178        if self.config.dialect == Some(DialectType::Redshift)
16179            && upper_name == "CONCAT_WS"
16180            && func.args.len() >= 2
16181        {
16182            let sep = &func.args[0];
16183            for (i, arg) in func.args.iter().skip(1).enumerate() {
16184                if i > 0 {
16185                    self.write(" || ");
16186                    self.generate_expression(sep)?;
16187                    self.write(" || ");
16188                }
16189                self.generate_expression(arg)?;
16190            }
16191            return Ok(());
16192        }
16193
16194        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16195        // Unit should be unquoted uppercase identifier
16196        if self.config.dialect == Some(DialectType::Redshift)
16197            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16198            && func.args.len() == 3
16199        {
16200            self.write_keyword("DATEDIFF");
16201            self.write("(");
16202            // First arg is unit - normalize to unquoted uppercase
16203            self.write_redshift_date_part(&func.args[0]);
16204            self.write(", ");
16205            self.generate_expression(&func.args[1])?;
16206            self.write(", ");
16207            self.generate_expression(&func.args[2])?;
16208            self.write(")");
16209            return Ok(());
16210        }
16211
16212        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16213        // Unit should be unquoted uppercase identifier
16214        if self.config.dialect == Some(DialectType::Redshift)
16215            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16216            && func.args.len() == 3
16217        {
16218            self.write_keyword("DATEADD");
16219            self.write("(");
16220            // First arg is unit - normalize to unquoted uppercase
16221            self.write_redshift_date_part(&func.args[0]);
16222            self.write(", ");
16223            self.generate_expression(&func.args[1])?;
16224            self.write(", ");
16225            self.generate_expression(&func.args[2])?;
16226            self.write(")");
16227            return Ok(());
16228        }
16229
16230        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16231        if upper_name == "UUID_STRING"
16232            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16233        {
16234            let func_name = match self.config.dialect {
16235                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16236                Some(DialectType::BigQuery) => "GENERATE_UUID",
16237                _ => "UUID",
16238            };
16239            self.write_keyword(func_name);
16240            self.write("()");
16241            return Ok(());
16242        }
16243
16244        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16245        // Unit should be quoted uppercase string
16246        if self.config.dialect == Some(DialectType::Redshift)
16247            && upper_name == "DATE_TRUNC"
16248            && func.args.len() == 2
16249        {
16250            self.write_keyword("DATE_TRUNC");
16251            self.write("(");
16252            // First arg is unit - normalize to quoted uppercase
16253            self.write_redshift_date_part_quoted(&func.args[0]);
16254            self.write(", ");
16255            self.generate_expression(&func.args[1])?;
16256            self.write(")");
16257            return Ok(());
16258        }
16259
16260        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16261        if matches!(
16262            self.config.dialect,
16263            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16264        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16265            && func.args.len() == 2
16266        {
16267            self.write_keyword("DATEPART");
16268            self.write("(");
16269            self.generate_expression(&func.args[0])?;
16270            self.write(", ");
16271            self.generate_expression(&func.args[1])?;
16272            self.write(")");
16273            return Ok(());
16274        }
16275
16276        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16277        if matches!(
16278            self.config.dialect,
16279            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16280        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16281            && func.args.len() == 2
16282        {
16283            self.write_keyword("EXTRACT");
16284            self.write("(");
16285            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16286            match &func.args[0] {
16287                Expression::Literal(crate::expressions::Literal::String(s)) => {
16288                    self.write(&s.to_lowercase());
16289                }
16290                _ => self.generate_expression(&func.args[0])?,
16291            }
16292            self.write_space();
16293            self.write_keyword("FROM");
16294            self.write_space();
16295            self.generate_expression(&func.args[1])?;
16296            self.write(")");
16297            return Ok(());
16298        }
16299
16300        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16301        // Also DATE literals in Dremio should be CAST(...AS DATE)
16302        if self.config.dialect == Some(DialectType::Dremio)
16303            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16304            && func.args.len() == 2
16305        {
16306            self.write_keyword("EXTRACT");
16307            self.write("(");
16308            self.generate_expression(&func.args[0])?;
16309            self.write_space();
16310            self.write_keyword("FROM");
16311            self.write_space();
16312            // For Dremio, DATE literals should become CAST('value' AS DATE)
16313            self.generate_dremio_date_expression(&func.args[1])?;
16314            self.write(")");
16315            return Ok(());
16316        }
16317
16318        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16319        if self.config.dialect == Some(DialectType::Dremio)
16320            && upper_name == "CURRENT_DATE_UTC"
16321            && func.args.is_empty()
16322        {
16323            self.write_keyword("CURRENT_DATE_UTC");
16324            return Ok(());
16325        }
16326
16327        // Dremio: DATETYPE(year, month, day) transformation
16328        // - If all args are integer literals: DATE('YYYY-MM-DD')
16329        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16330        if self.config.dialect == Some(DialectType::Dremio)
16331            && upper_name == "DATETYPE"
16332            && func.args.len() == 3
16333        {
16334            // Helper function to extract integer from number literal
16335            fn get_int_literal(expr: &Expression) -> Option<i64> {
16336                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16337                    s.parse::<i64>().ok()
16338                } else {
16339                    None
16340                }
16341            }
16342
16343            // Check if all arguments are integer literals
16344            if let (Some(year), Some(month), Some(day)) = (
16345                get_int_literal(&func.args[0]),
16346                get_int_literal(&func.args[1]),
16347                get_int_literal(&func.args[2]),
16348            ) {
16349                // All are integer literals: DATE('YYYY-MM-DD')
16350                self.write_keyword("DATE");
16351                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16352                return Ok(());
16353            }
16354
16355            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16356            self.write_keyword("CAST");
16357            self.write("(");
16358            self.write_keyword("CONCAT");
16359            self.write("(");
16360            self.generate_expression(&func.args[0])?;
16361            self.write(", '-', ");
16362            self.generate_expression(&func.args[1])?;
16363            self.write(", '-', ");
16364            self.generate_expression(&func.args[2])?;
16365            self.write(")");
16366            self.write_space();
16367            self.write_keyword("AS");
16368            self.write_space();
16369            self.write_keyword("DATE");
16370            self.write(")");
16371            return Ok(());
16372        }
16373
16374        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16375        // when it's not an integer literal
16376        let is_presto_like = matches!(
16377            self.config.dialect,
16378            Some(DialectType::Presto) | Some(DialectType::Trino)
16379        );
16380        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16381            self.write_keyword("DATE_ADD");
16382            self.write("(");
16383            // First arg: unit (pass through as-is, e.g., 'DAY')
16384            self.generate_expression(&func.args[0])?;
16385            self.write(", ");
16386            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16387            let interval = &func.args[1];
16388            let needs_cast = !self.returns_integer_type(interval);
16389            if needs_cast {
16390                self.write_keyword("CAST");
16391                self.write("(");
16392            }
16393            self.generate_expression(interval)?;
16394            if needs_cast {
16395                self.write_space();
16396                self.write_keyword("AS");
16397                self.write_space();
16398                self.write_keyword("BIGINT");
16399                self.write(")");
16400            }
16401            self.write(", ");
16402            // Third arg: date
16403            self.generate_expression(&func.args[2])?;
16404            self.write(")");
16405            return Ok(());
16406        }
16407
16408        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16409        let use_brackets = func.use_bracket_syntax;
16410
16411        // Special case: functions WITH ORDINALITY need special output order
16412        // Input: FUNC(args) WITH ORDINALITY
16413        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16414        // Output must be: FUNC(args) WITH ORDINALITY
16415        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16416        let output_name = if has_ordinality {
16417            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16418            self.normalize_func_name(base_name)
16419        } else {
16420            normalized_name.clone()
16421        };
16422
16423        // For qualified names (schema.function or object.method), preserve original case
16424        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16425        if func.name.contains('.') && !has_ordinality {
16426            // Don't normalize qualified functions - preserve original case
16427            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16428            if func.quoted {
16429                self.write("`");
16430                self.write(&func.name);
16431                self.write("`");
16432            } else {
16433                self.write(&func.name);
16434            }
16435        } else {
16436            self.write(&output_name);
16437        }
16438
16439        // If no_parens is true and there are no args, output just the function name
16440        // Unless the target dialect requires parens for this function
16441        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16442            let needs_parens = match upper_name.as_str() {
16443                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16444                    self.config.dialect,
16445                    Some(DialectType::Snowflake)
16446                        | Some(DialectType::Spark)
16447                        | Some(DialectType::Databricks)
16448                        | Some(DialectType::Hive)
16449                ),
16450                _ => false,
16451            };
16452            !needs_parens
16453        };
16454        if force_parens {
16455            // Output trailing comments
16456            for comment in &func.trailing_comments {
16457                self.write_space();
16458                self.write_formatted_comment(comment);
16459            }
16460            return Ok(());
16461        }
16462
16463        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16464        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16465            self.write(" (");
16466        } else if use_brackets {
16467            self.write("[");
16468        } else {
16469            self.write("(");
16470        }
16471        if func.distinct {
16472            self.write_keyword("DISTINCT");
16473            self.write_space();
16474        }
16475
16476        // Check if arguments should be split onto multiple lines (pretty + too wide)
16477        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16478            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16479        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16480        let is_grouping_func =
16481            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16482        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16483            if is_grouping_func {
16484                true
16485            } else {
16486                // Pre-render arguments to check total width
16487                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16488                for arg in &func.args {
16489                    let mut temp_gen = Generator::with_config(self.config.clone());
16490                    temp_gen.config.pretty = false; // Don't recurse into pretty
16491                    temp_gen.generate_expression(arg)?;
16492                    expr_strings.push(temp_gen.output);
16493                }
16494                self.too_wide(&expr_strings)
16495            }
16496        } else {
16497            false
16498        };
16499
16500        if should_split {
16501            // Split onto multiple lines
16502            self.write_newline();
16503            self.indent_level += 1;
16504            for (i, arg) in func.args.iter().enumerate() {
16505                self.write_indent();
16506                self.generate_expression(arg)?;
16507                if i + 1 < func.args.len() {
16508                    self.write(",");
16509                }
16510                self.write_newline();
16511            }
16512            self.indent_level -= 1;
16513            self.write_indent();
16514        } else {
16515            // All on one line
16516            for (i, arg) in func.args.iter().enumerate() {
16517                if i > 0 {
16518                    self.write(", ");
16519                }
16520                self.generate_expression(arg)?;
16521            }
16522        }
16523
16524        if use_brackets {
16525            self.write("]");
16526        } else {
16527            self.write(")");
16528        }
16529        // Append WITH ORDINALITY after closing paren for table-valued functions
16530        if has_ordinality {
16531            self.write_space();
16532            self.write_keyword("WITH ORDINALITY");
16533        }
16534        // Output trailing comments
16535        for comment in &func.trailing_comments {
16536            self.write_space();
16537            self.write_formatted_comment(comment);
16538        }
16539        Ok(())
16540    }
16541
16542    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16543        // Normalize function name based on dialect settings
16544        let mut normalized_name = self.normalize_func_name(&func.name);
16545
16546        // Dialect-specific name mappings for aggregate functions
16547        let upper = normalized_name.to_uppercase();
16548        if upper == "MAX_BY" || upper == "MIN_BY" {
16549            let is_max = upper == "MAX_BY";
16550            match self.config.dialect {
16551                Some(DialectType::ClickHouse) => {
16552                    normalized_name = if is_max {
16553                        "argMax".to_string()
16554                    } else {
16555                        "argMin".to_string()
16556                    };
16557                }
16558                Some(DialectType::DuckDB) => {
16559                    normalized_name = if is_max {
16560                        "ARG_MAX".to_string()
16561                    } else {
16562                        "ARG_MIN".to_string()
16563                    };
16564                }
16565                _ => {}
16566            }
16567        }
16568        self.write(&normalized_name);
16569        self.write("(");
16570        if func.distinct {
16571            self.write_keyword("DISTINCT");
16572            self.write_space();
16573        }
16574
16575        // Check if we need to transform multi-arg COUNT DISTINCT
16576        // When dialect doesn't support multi_arg_distinct, transform:
16577        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16578        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16579        let needs_multi_arg_transform =
16580            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16581
16582        if needs_multi_arg_transform {
16583            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16584            self.write_keyword("CASE");
16585            for arg in &func.args {
16586                self.write_space();
16587                self.write_keyword("WHEN");
16588                self.write_space();
16589                self.generate_expression(arg)?;
16590                self.write_space();
16591                self.write_keyword("IS NULL THEN NULL");
16592            }
16593            self.write_space();
16594            self.write_keyword("ELSE");
16595            self.write(" (");
16596            for (i, arg) in func.args.iter().enumerate() {
16597                if i > 0 {
16598                    self.write(", ");
16599                }
16600                self.generate_expression(arg)?;
16601            }
16602            self.write(")");
16603            self.write_space();
16604            self.write_keyword("END");
16605        } else {
16606            for (i, arg) in func.args.iter().enumerate() {
16607                if i > 0 {
16608                    self.write(", ");
16609                }
16610                self.generate_expression(arg)?;
16611            }
16612        }
16613
16614        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16615        if self.config.ignore_nulls_in_func
16616            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16617        {
16618            if let Some(ignore) = func.ignore_nulls {
16619                self.write_space();
16620                if ignore {
16621                    self.write_keyword("IGNORE NULLS");
16622                } else {
16623                    self.write_keyword("RESPECT NULLS");
16624                }
16625            }
16626        }
16627
16628        // ORDER BY inside aggregate
16629        if !func.order_by.is_empty() {
16630            self.write_space();
16631            self.write_keyword("ORDER BY");
16632            self.write_space();
16633            for (i, ord) in func.order_by.iter().enumerate() {
16634                if i > 0 {
16635                    self.write(", ");
16636                }
16637                self.generate_ordered(ord)?;
16638            }
16639        }
16640
16641        // LIMIT inside aggregate
16642        if let Some(limit) = &func.limit {
16643            self.write_space();
16644            self.write_keyword("LIMIT");
16645            self.write_space();
16646            // Check if this is a Tuple representing LIMIT offset, count
16647            if let Expression::Tuple(t) = limit.as_ref() {
16648                if t.expressions.len() == 2 {
16649                    self.generate_expression(&t.expressions[0])?;
16650                    self.write(", ");
16651                    self.generate_expression(&t.expressions[1])?;
16652                } else {
16653                    self.generate_expression(limit)?;
16654                }
16655            } else {
16656                self.generate_expression(limit)?;
16657            }
16658        }
16659
16660        self.write(")");
16661
16662        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16663        if !self.config.ignore_nulls_in_func
16664            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16665        {
16666            if let Some(ignore) = func.ignore_nulls {
16667                self.write_space();
16668                if ignore {
16669                    self.write_keyword("IGNORE NULLS");
16670                } else {
16671                    self.write_keyword("RESPECT NULLS");
16672                }
16673            }
16674        }
16675
16676        if let Some(filter) = &func.filter {
16677            self.write_space();
16678            self.write_keyword("FILTER");
16679            self.write("(");
16680            self.write_keyword("WHERE");
16681            self.write_space();
16682            self.generate_expression(filter)?;
16683            self.write(")");
16684        }
16685
16686        Ok(())
16687    }
16688
16689    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16690        self.generate_expression(&wf.this)?;
16691
16692        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16693        if let Some(keep) = &wf.keep {
16694            self.write_space();
16695            self.write_keyword("KEEP");
16696            self.write(" (");
16697            self.write_keyword("DENSE_RANK");
16698            self.write_space();
16699            if keep.first {
16700                self.write_keyword("FIRST");
16701            } else {
16702                self.write_keyword("LAST");
16703            }
16704            self.write_space();
16705            self.write_keyword("ORDER BY");
16706            self.write_space();
16707            for (i, ord) in keep.order_by.iter().enumerate() {
16708                if i > 0 {
16709                    self.write(", ");
16710                }
16711                self.generate_ordered(ord)?;
16712            }
16713            self.write(")");
16714        }
16715
16716        // Check if there's any OVER clause content
16717        let has_over = !wf.over.partition_by.is_empty()
16718            || !wf.over.order_by.is_empty()
16719            || wf.over.frame.is_some()
16720            || wf.over.window_name.is_some();
16721
16722        // Only output OVER if there's actual window specification (not just KEEP alone)
16723        if has_over {
16724            self.write_space();
16725            self.write_keyword("OVER");
16726
16727            // Check if this is just a bare named window reference (no parens needed)
16728            let has_specs = !wf.over.partition_by.is_empty()
16729                || !wf.over.order_by.is_empty()
16730                || wf.over.frame.is_some();
16731
16732            if wf.over.window_name.is_some() && !has_specs {
16733                // OVER window_name (without parentheses)
16734                self.write_space();
16735                self.write(&wf.over.window_name.as_ref().unwrap().name);
16736            } else {
16737                // OVER (...) or OVER (window_name ...)
16738                self.write(" (");
16739                self.generate_over(&wf.over)?;
16740                self.write(")");
16741            }
16742        } else if wf.keep.is_none() {
16743            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16744            self.write_space();
16745            self.write_keyword("OVER");
16746            self.write(" ()");
16747        }
16748
16749        Ok(())
16750    }
16751
16752    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16753    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16754        self.generate_expression(&wg.this)?;
16755        self.write_space();
16756        self.write_keyword("WITHIN GROUP");
16757        self.write(" (");
16758        self.write_keyword("ORDER BY");
16759        self.write_space();
16760        for (i, ord) in wg.order_by.iter().enumerate() {
16761            if i > 0 {
16762                self.write(", ");
16763            }
16764            self.generate_ordered(ord)?;
16765        }
16766        self.write(")");
16767        Ok(())
16768    }
16769
16770    /// Generate the contents of an OVER clause (without parentheses)
16771    fn generate_over(&mut self, over: &Over) -> Result<()> {
16772        let mut has_content = false;
16773
16774        // Named window reference
16775        if let Some(name) = &over.window_name {
16776            self.write(&name.name);
16777            has_content = true;
16778        }
16779
16780        // PARTITION BY
16781        if !over.partition_by.is_empty() {
16782            if has_content {
16783                self.write_space();
16784            }
16785            self.write_keyword("PARTITION BY");
16786            self.write_space();
16787            for (i, expr) in over.partition_by.iter().enumerate() {
16788                if i > 0 {
16789                    self.write(", ");
16790                }
16791                self.generate_expression(expr)?;
16792            }
16793            has_content = true;
16794        }
16795
16796        // ORDER BY
16797        if !over.order_by.is_empty() {
16798            if has_content {
16799                self.write_space();
16800            }
16801            self.write_keyword("ORDER BY");
16802            self.write_space();
16803            for (i, ordered) in over.order_by.iter().enumerate() {
16804                if i > 0 {
16805                    self.write(", ");
16806                }
16807                self.generate_ordered(ordered)?;
16808            }
16809            has_content = true;
16810        }
16811
16812        // Window frame
16813        if let Some(frame) = &over.frame {
16814            if has_content {
16815                self.write_space();
16816            }
16817            self.generate_window_frame(frame)?;
16818        }
16819
16820        Ok(())
16821    }
16822
16823    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16824        // Exasol uses lowercase for frame kind (rows/range/groups)
16825        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16826
16827        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16828        if !lowercase_frame {
16829            if let Some(kind_text) = &frame.kind_text {
16830                self.write(kind_text);
16831            } else {
16832                match frame.kind {
16833                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16834                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16835                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16836                }
16837            }
16838        } else {
16839            match frame.kind {
16840                WindowFrameKind::Rows => self.write("rows"),
16841                WindowFrameKind::Range => self.write("range"),
16842                WindowFrameKind::Groups => self.write("groups"),
16843            }
16844        }
16845
16846        // Use BETWEEN format only when there's an explicit end bound,
16847        // or when normalize_window_frame_between is enabled and the start is a directional bound
16848        self.write_space();
16849        let should_normalize = self.config.normalize_window_frame_between
16850            && frame.end.is_none()
16851            && matches!(
16852                frame.start,
16853                WindowFrameBound::Preceding(_)
16854                    | WindowFrameBound::Following(_)
16855                    | WindowFrameBound::UnboundedPreceding
16856                    | WindowFrameBound::UnboundedFollowing
16857            );
16858
16859        if let Some(end) = &frame.end {
16860            // BETWEEN format: RANGE BETWEEN start AND end
16861            self.write_keyword("BETWEEN");
16862            self.write_space();
16863            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16864            self.write_space();
16865            self.write_keyword("AND");
16866            self.write_space();
16867            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16868        } else if should_normalize {
16869            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16870            self.write_keyword("BETWEEN");
16871            self.write_space();
16872            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16873            self.write_space();
16874            self.write_keyword("AND");
16875            self.write_space();
16876            self.write_keyword("CURRENT ROW");
16877        } else {
16878            // Single bound format: RANGE CURRENT ROW
16879            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16880        }
16881
16882        // EXCLUDE clause
16883        if let Some(exclude) = &frame.exclude {
16884            self.write_space();
16885            self.write_keyword("EXCLUDE");
16886            self.write_space();
16887            match exclude {
16888                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16889                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16890                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16891                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16892            }
16893        }
16894
16895        Ok(())
16896    }
16897
16898    fn generate_window_frame_bound(
16899        &mut self,
16900        bound: &WindowFrameBound,
16901        side_text: Option<&str>,
16902    ) -> Result<()> {
16903        // Exasol uses lowercase for preceding/following
16904        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16905
16906        match bound {
16907            WindowFrameBound::CurrentRow => {
16908                self.write_keyword("CURRENT ROW");
16909            }
16910            WindowFrameBound::UnboundedPreceding => {
16911                self.write_keyword("UNBOUNDED");
16912                self.write_space();
16913                if lowercase_frame {
16914                    self.write("preceding");
16915                } else if let Some(text) = side_text {
16916                    self.write(text);
16917                } else {
16918                    self.write_keyword("PRECEDING");
16919                }
16920            }
16921            WindowFrameBound::UnboundedFollowing => {
16922                self.write_keyword("UNBOUNDED");
16923                self.write_space();
16924                if lowercase_frame {
16925                    self.write("following");
16926                } else if let Some(text) = side_text {
16927                    self.write(text);
16928                } else {
16929                    self.write_keyword("FOLLOWING");
16930                }
16931            }
16932            WindowFrameBound::Preceding(expr) => {
16933                self.generate_expression(expr)?;
16934                self.write_space();
16935                if lowercase_frame {
16936                    self.write("preceding");
16937                } else if let Some(text) = side_text {
16938                    self.write(text);
16939                } else {
16940                    self.write_keyword("PRECEDING");
16941                }
16942            }
16943            WindowFrameBound::Following(expr) => {
16944                self.generate_expression(expr)?;
16945                self.write_space();
16946                if lowercase_frame {
16947                    self.write("following");
16948                } else if let Some(text) = side_text {
16949                    self.write(text);
16950                } else {
16951                    self.write_keyword("FOLLOWING");
16952                }
16953            }
16954            WindowFrameBound::BarePreceding => {
16955                if lowercase_frame {
16956                    self.write("preceding");
16957                } else if let Some(text) = side_text {
16958                    self.write(text);
16959                } else {
16960                    self.write_keyword("PRECEDING");
16961                }
16962            }
16963            WindowFrameBound::BareFollowing => {
16964                if lowercase_frame {
16965                    self.write("following");
16966                } else if let Some(text) = side_text {
16967                    self.write(text);
16968                } else {
16969                    self.write_keyword("FOLLOWING");
16970                }
16971            }
16972            WindowFrameBound::Value(expr) => {
16973                // Bare numeric bound without PRECEDING/FOLLOWING
16974                self.generate_expression(expr)?;
16975            }
16976        }
16977        Ok(())
16978    }
16979
16980    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16981        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16982        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16983        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16984            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16985            && !matches!(&interval.this, Some(Expression::Literal(_)));
16986
16987        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16988        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16989        if self.config.single_string_interval {
16990            if let (
16991                Some(Expression::Literal(Literal::String(ref val))),
16992                Some(IntervalUnitSpec::Simple {
16993                    ref unit,
16994                    ref use_plural,
16995                }),
16996            ) = (&interval.this, &interval.unit)
16997            {
16998                self.write_keyword("INTERVAL");
16999                self.write_space();
17000                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17001                let unit_str = self.interval_unit_str(unit, effective_plural);
17002                self.write("'");
17003                self.write(val);
17004                self.write(" ");
17005                self.write(&unit_str);
17006                self.write("'");
17007                return Ok(());
17008            }
17009        }
17010
17011        if !skip_interval_keyword {
17012            self.write_keyword("INTERVAL");
17013        }
17014
17015        // Generate value if present
17016        if let Some(ref value) = interval.this {
17017            if !skip_interval_keyword {
17018                self.write_space();
17019            }
17020            // If the value is a complex expression (not a literal/column/function call)
17021            // and there's a unit, wrap it in parentheses
17022            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17023            let needs_parens = interval.unit.is_some()
17024                && matches!(
17025                    value,
17026                    Expression::Add(_)
17027                        | Expression::Sub(_)
17028                        | Expression::Mul(_)
17029                        | Expression::Div(_)
17030                        | Expression::Mod(_)
17031                        | Expression::BitwiseAnd(_)
17032                        | Expression::BitwiseOr(_)
17033                        | Expression::BitwiseXor(_)
17034                );
17035            if needs_parens {
17036                self.write("(");
17037            }
17038            self.generate_expression(value)?;
17039            if needs_parens {
17040                self.write(")");
17041            }
17042        }
17043
17044        // Generate unit if present
17045        if let Some(ref unit_spec) = interval.unit {
17046            self.write_space();
17047            self.write_interval_unit_spec(unit_spec)?;
17048        }
17049
17050        Ok(())
17051    }
17052
17053    /// Return the string representation of an interval unit
17054    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17055        match (unit, use_plural) {
17056            (IntervalUnit::Year, false) => "YEAR",
17057            (IntervalUnit::Year, true) => "YEARS",
17058            (IntervalUnit::Quarter, false) => "QUARTER",
17059            (IntervalUnit::Quarter, true) => "QUARTERS",
17060            (IntervalUnit::Month, false) => "MONTH",
17061            (IntervalUnit::Month, true) => "MONTHS",
17062            (IntervalUnit::Week, false) => "WEEK",
17063            (IntervalUnit::Week, true) => "WEEKS",
17064            (IntervalUnit::Day, false) => "DAY",
17065            (IntervalUnit::Day, true) => "DAYS",
17066            (IntervalUnit::Hour, false) => "HOUR",
17067            (IntervalUnit::Hour, true) => "HOURS",
17068            (IntervalUnit::Minute, false) => "MINUTE",
17069            (IntervalUnit::Minute, true) => "MINUTES",
17070            (IntervalUnit::Second, false) => "SECOND",
17071            (IntervalUnit::Second, true) => "SECONDS",
17072            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17073            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17074            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17075            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17076            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17077            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17078        }
17079    }
17080
17081    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17082        match unit_spec {
17083            IntervalUnitSpec::Simple { unit, use_plural } => {
17084                // If dialect doesn't allow plural forms, force singular
17085                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17086                self.write_simple_interval_unit(unit, effective_plural);
17087            }
17088            IntervalUnitSpec::Span(span) => {
17089                self.write_simple_interval_unit(&span.this, false);
17090                self.write_space();
17091                self.write_keyword("TO");
17092                self.write_space();
17093                self.write_simple_interval_unit(&span.expression, false);
17094            }
17095            IntervalUnitSpec::ExprSpan(span) => {
17096                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17097                self.generate_expression(&span.this)?;
17098                self.write_space();
17099                self.write_keyword("TO");
17100                self.write_space();
17101                self.generate_expression(&span.expression)?;
17102            }
17103            IntervalUnitSpec::Expr(expr) => {
17104                self.generate_expression(expr)?;
17105            }
17106        }
17107        Ok(())
17108    }
17109
17110    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17111        // Output interval unit, respecting plural preference
17112        match (unit, use_plural) {
17113            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17114            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17115            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17116            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17117            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17118            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17119            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17120            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17121            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17122            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17123            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17124            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17125            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17126            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17127            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17128            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17129            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17130            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17131            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17132            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17133            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17134            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17135        }
17136    }
17137
17138    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17139    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17140    fn write_redshift_date_part(&mut self, expr: &Expression) {
17141        let part_str = self.extract_date_part_string(expr);
17142        if let Some(part) = part_str {
17143            let normalized = self.normalize_date_part(&part);
17144            self.write_keyword(&normalized);
17145        } else {
17146            // If we can't extract a date part string, fall back to generating the expression
17147            let _ = self.generate_expression(expr);
17148        }
17149    }
17150
17151    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17152    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17153    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17154        let part_str = self.extract_date_part_string(expr);
17155        if let Some(part) = part_str {
17156            let normalized = self.normalize_date_part(&part);
17157            self.write("'");
17158            self.write(&normalized);
17159            self.write("'");
17160        } else {
17161            // If we can't extract a date part string, fall back to generating the expression
17162            let _ = self.generate_expression(expr);
17163        }
17164    }
17165
17166    /// Extract date part string from expression (handles string literals and identifiers)
17167    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17168        match expr {
17169            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17170            Expression::Identifier(id) => Some(id.name.clone()),
17171            Expression::Column(col) if col.table.is_none() => {
17172                // Simple column reference without table prefix, treat as identifier
17173                Some(col.name.name.clone())
17174            }
17175            _ => None,
17176        }
17177    }
17178
17179    /// Normalize date part to uppercase singular form
17180    /// days -> DAY, months -> MONTH, etc.
17181    fn normalize_date_part(&self, part: &str) -> String {
17182        let lower = part.to_lowercase();
17183        match lower.as_str() {
17184            "day" | "days" | "d" => "DAY".to_string(),
17185            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17186            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17187            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17188            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17189            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17190            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17191            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17192            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17193            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17194            _ => part.to_uppercase(),
17195        }
17196    }
17197
17198    fn write_datetime_field(&mut self, field: &DateTimeField) {
17199        match field {
17200            DateTimeField::Year => self.write_keyword("YEAR"),
17201            DateTimeField::Month => self.write_keyword("MONTH"),
17202            DateTimeField::Day => self.write_keyword("DAY"),
17203            DateTimeField::Hour => self.write_keyword("HOUR"),
17204            DateTimeField::Minute => self.write_keyword("MINUTE"),
17205            DateTimeField::Second => self.write_keyword("SECOND"),
17206            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17207            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17208            DateTimeField::DayOfWeek => {
17209                let name = match self.config.dialect {
17210                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17211                    _ => "DOW",
17212                };
17213                self.write_keyword(name);
17214            }
17215            DateTimeField::DayOfYear => {
17216                let name = match self.config.dialect {
17217                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17218                    _ => "DOY",
17219                };
17220                self.write_keyword(name);
17221            }
17222            DateTimeField::Week => self.write_keyword("WEEK"),
17223            DateTimeField::WeekWithModifier(modifier) => {
17224                self.write_keyword("WEEK");
17225                self.write("(");
17226                self.write(modifier);
17227                self.write(")");
17228            }
17229            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17230            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17231            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17232            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17233            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17234            DateTimeField::Date => self.write_keyword("DATE"),
17235            DateTimeField::Time => self.write_keyword("TIME"),
17236            DateTimeField::Custom(name) => self.write(name),
17237        }
17238    }
17239
17240    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17241    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17242        match field {
17243            DateTimeField::Year => self.write("year"),
17244            DateTimeField::Month => self.write("month"),
17245            DateTimeField::Day => self.write("day"),
17246            DateTimeField::Hour => self.write("hour"),
17247            DateTimeField::Minute => self.write("minute"),
17248            DateTimeField::Second => self.write("second"),
17249            DateTimeField::Millisecond => self.write("millisecond"),
17250            DateTimeField::Microsecond => self.write("microsecond"),
17251            DateTimeField::DayOfWeek => self.write("dow"),
17252            DateTimeField::DayOfYear => self.write("doy"),
17253            DateTimeField::Week => self.write("week"),
17254            DateTimeField::WeekWithModifier(modifier) => {
17255                self.write("week(");
17256                self.write(modifier);
17257                self.write(")");
17258            }
17259            DateTimeField::Quarter => self.write("quarter"),
17260            DateTimeField::Epoch => self.write("epoch"),
17261            DateTimeField::Timezone => self.write("timezone"),
17262            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17263            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17264            DateTimeField::Date => self.write("date"),
17265            DateTimeField::Time => self.write("time"),
17266            DateTimeField::Custom(name) => self.write(name),
17267        }
17268    }
17269
17270    // Helper function generators
17271
17272    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17273        self.write_keyword(name);
17274        self.write("(");
17275        self.generate_expression(arg)?;
17276        self.write(")");
17277        Ok(())
17278    }
17279
17280    /// Generate a unary function, using the original name if available for round-trip preservation
17281    fn generate_unary_func(
17282        &mut self,
17283        default_name: &str,
17284        f: &crate::expressions::UnaryFunc,
17285    ) -> Result<()> {
17286        let name = f.original_name.as_deref().unwrap_or(default_name);
17287        self.write_keyword(name);
17288        self.write("(");
17289        self.generate_expression(&f.this)?;
17290        self.write(")");
17291        Ok(())
17292    }
17293
17294    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17295    fn generate_sqrt_cbrt(
17296        &mut self,
17297        f: &crate::expressions::UnaryFunc,
17298        func_name: &str,
17299        _op: &str,
17300    ) -> Result<()> {
17301        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17302        // Always use function syntax for consistency
17303        self.write_keyword(func_name);
17304        self.write("(");
17305        self.generate_expression(&f.this)?;
17306        self.write(")");
17307        Ok(())
17308    }
17309
17310    fn generate_binary_func(
17311        &mut self,
17312        name: &str,
17313        arg1: &Expression,
17314        arg2: &Expression,
17315    ) -> Result<()> {
17316        self.write_keyword(name);
17317        self.write("(");
17318        self.generate_expression(arg1)?;
17319        self.write(", ");
17320        self.generate_expression(arg2)?;
17321        self.write(")");
17322        Ok(())
17323    }
17324
17325    /// Generate CHAR/CHR function with optional USING charset
17326    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17327    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17328    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17329        // Use stored name if available, otherwise default to CHAR
17330        let func_name = f.name.as_deref().unwrap_or("CHAR");
17331        self.write_keyword(func_name);
17332        self.write("(");
17333        for (i, arg) in f.args.iter().enumerate() {
17334            if i > 0 {
17335                self.write(", ");
17336            }
17337            self.generate_expression(arg)?;
17338        }
17339        if let Some(ref charset) = f.charset {
17340            self.write(" ");
17341            self.write_keyword("USING");
17342            self.write(" ");
17343            self.write(charset);
17344        }
17345        self.write(")");
17346        Ok(())
17347    }
17348
17349    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17350        use crate::dialects::DialectType;
17351
17352        match self.config.dialect {
17353            Some(DialectType::Teradata) => {
17354                // Teradata uses ** operator for exponentiation
17355                self.generate_expression(&f.this)?;
17356                self.write(" ** ");
17357                self.generate_expression(&f.expression)?;
17358                Ok(())
17359            }
17360            _ => {
17361                // Other dialects use POWER function
17362                self.generate_binary_func("POWER", &f.this, &f.expression)
17363            }
17364        }
17365    }
17366
17367    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17368        self.write_func_name(name);
17369        self.write("(");
17370        for (i, arg) in args.iter().enumerate() {
17371            if i > 0 {
17372                self.write(", ");
17373            }
17374            self.generate_expression(arg)?;
17375        }
17376        self.write(")");
17377        Ok(())
17378    }
17379
17380    // String function generators
17381
17382    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17383        self.write_keyword("CONCAT_WS");
17384        self.write("(");
17385        self.generate_expression(&f.separator)?;
17386        for expr in &f.expressions {
17387            self.write(", ");
17388            self.generate_expression(expr)?;
17389        }
17390        self.write(")");
17391        Ok(())
17392    }
17393
17394    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17395        if let Expression::Concat(op) = expr {
17396            Self::collect_concat_operands(&op.left, out);
17397            Self::collect_concat_operands(&op.right, out);
17398        } else {
17399            out.push(expr);
17400        }
17401    }
17402
17403    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
17404        let mut operands = Vec::new();
17405        Self::collect_concat_operands(&op.left, &mut operands);
17406        Self::collect_concat_operands(&op.right, &mut operands);
17407
17408        self.write_keyword("CONCAT");
17409        self.write("(");
17410        for (i, operand) in operands.iter().enumerate() {
17411            if i > 0 {
17412                self.write(", ");
17413            }
17414            self.generate_expression(operand)?;
17415        }
17416        self.write(")");
17417        Ok(())
17418    }
17419
17420    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17421        if let Expression::DPipe(dpipe) = expr {
17422            Self::collect_dpipe_operands(&dpipe.this, out);
17423            Self::collect_dpipe_operands(&dpipe.expression, out);
17424        } else {
17425            out.push(expr);
17426        }
17427    }
17428
17429    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
17430        let mut operands = Vec::new();
17431        Self::collect_dpipe_operands(&e.this, &mut operands);
17432        Self::collect_dpipe_operands(&e.expression, &mut operands);
17433
17434        self.write_keyword("CONCAT");
17435        self.write("(");
17436        for (i, operand) in operands.iter().enumerate() {
17437            if i > 0 {
17438                self.write(", ");
17439            }
17440            self.generate_expression(operand)?;
17441        }
17442        self.write(")");
17443        Ok(())
17444    }
17445
17446    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17447        // Oracle uses SUBSTR; most others use SUBSTRING
17448        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17449        if is_oracle {
17450            self.write_keyword("SUBSTR");
17451        } else {
17452            self.write_keyword("SUBSTRING");
17453        }
17454        self.write("(");
17455        self.generate_expression(&f.this)?;
17456        // PostgreSQL always uses FROM/FOR syntax
17457        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17458        // Spark/Hive use comma syntax, not FROM/FOR syntax
17459        let use_comma_syntax = matches!(
17460            self.config.dialect,
17461            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17462        );
17463        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17464            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17465            self.write_space();
17466            self.write_keyword("FROM");
17467            self.write_space();
17468            self.generate_expression(&f.start)?;
17469            if let Some(length) = &f.length {
17470                self.write_space();
17471                self.write_keyword("FOR");
17472                self.write_space();
17473                self.generate_expression(length)?;
17474            }
17475        } else {
17476            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17477            self.write(", ");
17478            self.generate_expression(&f.start)?;
17479            if let Some(length) = &f.length {
17480                self.write(", ");
17481                self.generate_expression(length)?;
17482            }
17483        }
17484        self.write(")");
17485        Ok(())
17486    }
17487
17488    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17489        self.write_keyword("OVERLAY");
17490        self.write("(");
17491        self.generate_expression(&f.this)?;
17492        self.write_space();
17493        self.write_keyword("PLACING");
17494        self.write_space();
17495        self.generate_expression(&f.replacement)?;
17496        self.write_space();
17497        self.write_keyword("FROM");
17498        self.write_space();
17499        self.generate_expression(&f.from)?;
17500        if let Some(length) = &f.length {
17501            self.write_space();
17502            self.write_keyword("FOR");
17503            self.write_space();
17504            self.generate_expression(length)?;
17505        }
17506        self.write(")");
17507        Ok(())
17508    }
17509
17510    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17511        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17512        // when no characters are specified (PostgreSQL style)
17513        if f.position_explicit && f.characters.is_none() {
17514            match f.position {
17515                TrimPosition::Leading => {
17516                    self.write_keyword("LTRIM");
17517                    self.write("(");
17518                    self.generate_expression(&f.this)?;
17519                    self.write(")");
17520                    return Ok(());
17521                }
17522                TrimPosition::Trailing => {
17523                    self.write_keyword("RTRIM");
17524                    self.write("(");
17525                    self.generate_expression(&f.this)?;
17526                    self.write(")");
17527                    return Ok(());
17528                }
17529                TrimPosition::Both => {
17530                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17531                    // Fall through to standard TRIM handling
17532                }
17533            }
17534        }
17535
17536        self.write_keyword("TRIM");
17537        self.write("(");
17538        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17539        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17540        let force_standard = f.characters.is_some()
17541            && !f.sql_standard_syntax
17542            && matches!(
17543                self.config.dialect,
17544                Some(DialectType::Hive)
17545                    | Some(DialectType::Spark)
17546                    | Some(DialectType::Databricks)
17547                    | Some(DialectType::ClickHouse)
17548            );
17549        let use_standard = (f.sql_standard_syntax || force_standard)
17550            && !(f.position_explicit
17551                && f.characters.is_none()
17552                && matches!(f.position, TrimPosition::Both));
17553        if use_standard {
17554            // SQL standard syntax: TRIM(BOTH chars FROM str)
17555            // Only output position if it was explicitly specified
17556            if f.position_explicit {
17557                match f.position {
17558                    TrimPosition::Both => self.write_keyword("BOTH"),
17559                    TrimPosition::Leading => self.write_keyword("LEADING"),
17560                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17561                }
17562                self.write_space();
17563            }
17564            if let Some(chars) = &f.characters {
17565                self.generate_expression(chars)?;
17566                self.write_space();
17567            }
17568            self.write_keyword("FROM");
17569            self.write_space();
17570            self.generate_expression(&f.this)?;
17571        } else {
17572            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17573            self.generate_expression(&f.this)?;
17574            if let Some(chars) = &f.characters {
17575                self.write(", ");
17576                self.generate_expression(chars)?;
17577            }
17578        }
17579        self.write(")");
17580        Ok(())
17581    }
17582
17583    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17584        self.write_keyword("REPLACE");
17585        self.write("(");
17586        self.generate_expression(&f.this)?;
17587        self.write(", ");
17588        self.generate_expression(&f.old)?;
17589        self.write(", ");
17590        self.generate_expression(&f.new)?;
17591        self.write(")");
17592        Ok(())
17593    }
17594
17595    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17596        self.write_keyword(name);
17597        self.write("(");
17598        self.generate_expression(&f.this)?;
17599        self.write(", ");
17600        self.generate_expression(&f.length)?;
17601        self.write(")");
17602        Ok(())
17603    }
17604
17605    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17606        self.write_keyword("REPEAT");
17607        self.write("(");
17608        self.generate_expression(&f.this)?;
17609        self.write(", ");
17610        self.generate_expression(&f.times)?;
17611        self.write(")");
17612        Ok(())
17613    }
17614
17615    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17616        self.write_keyword(name);
17617        self.write("(");
17618        self.generate_expression(&f.this)?;
17619        self.write(", ");
17620        self.generate_expression(&f.length)?;
17621        if let Some(fill) = &f.fill {
17622            self.write(", ");
17623            self.generate_expression(fill)?;
17624        }
17625        self.write(")");
17626        Ok(())
17627    }
17628
17629    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17630        self.write_keyword("SPLIT");
17631        self.write("(");
17632        self.generate_expression(&f.this)?;
17633        self.write(", ");
17634        self.generate_expression(&f.delimiter)?;
17635        self.write(")");
17636        Ok(())
17637    }
17638
17639    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17640        use crate::dialects::DialectType;
17641        // PostgreSQL uses ~ operator for regex matching
17642        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17643            self.generate_expression(&f.this)?;
17644            self.write(" ~ ");
17645            self.generate_expression(&f.pattern)?;
17646        } else if matches!(
17647            self.config.dialect,
17648            Some(DialectType::SingleStore)
17649                | Some(DialectType::Spark)
17650                | Some(DialectType::Hive)
17651                | Some(DialectType::Databricks)
17652        ) && f.flags.is_none()
17653        {
17654            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17655            self.generate_expression(&f.this)?;
17656            self.write_keyword(" RLIKE ");
17657            self.generate_expression(&f.pattern)?;
17658        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17659            // StarRocks uses REGEXP function syntax
17660            self.write_keyword("REGEXP");
17661            self.write("(");
17662            self.generate_expression(&f.this)?;
17663            self.write(", ");
17664            self.generate_expression(&f.pattern)?;
17665            if let Some(flags) = &f.flags {
17666                self.write(", ");
17667                self.generate_expression(flags)?;
17668            }
17669            self.write(")");
17670        } else {
17671            self.write_keyword("REGEXP_LIKE");
17672            self.write("(");
17673            self.generate_expression(&f.this)?;
17674            self.write(", ");
17675            self.generate_expression(&f.pattern)?;
17676            if let Some(flags) = &f.flags {
17677                self.write(", ");
17678                self.generate_expression(flags)?;
17679            }
17680            self.write(")");
17681        }
17682        Ok(())
17683    }
17684
17685    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17686        self.write_keyword("REGEXP_REPLACE");
17687        self.write("(");
17688        self.generate_expression(&f.this)?;
17689        self.write(", ");
17690        self.generate_expression(&f.pattern)?;
17691        self.write(", ");
17692        self.generate_expression(&f.replacement)?;
17693        if let Some(flags) = &f.flags {
17694            self.write(", ");
17695            self.generate_expression(flags)?;
17696        }
17697        self.write(")");
17698        Ok(())
17699    }
17700
17701    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17702        self.write_keyword("REGEXP_EXTRACT");
17703        self.write("(");
17704        self.generate_expression(&f.this)?;
17705        self.write(", ");
17706        self.generate_expression(&f.pattern)?;
17707        if let Some(group) = &f.group {
17708            self.write(", ");
17709            self.generate_expression(group)?;
17710        }
17711        self.write(")");
17712        Ok(())
17713    }
17714
17715    // Math function generators
17716
17717    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17718        self.write_keyword("ROUND");
17719        self.write("(");
17720        self.generate_expression(&f.this)?;
17721        if let Some(decimals) = &f.decimals {
17722            self.write(", ");
17723            self.generate_expression(decimals)?;
17724        }
17725        self.write(")");
17726        Ok(())
17727    }
17728
17729    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17730        self.write_keyword("FLOOR");
17731        self.write("(");
17732        self.generate_expression(&f.this)?;
17733        // Handle Druid-style FLOOR(time TO unit) syntax
17734        if let Some(to) = &f.to {
17735            self.write(" ");
17736            self.write_keyword("TO");
17737            self.write(" ");
17738            self.generate_expression(to)?;
17739        } else if let Some(scale) = &f.scale {
17740            self.write(", ");
17741            self.generate_expression(scale)?;
17742        }
17743        self.write(")");
17744        Ok(())
17745    }
17746
17747    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17748        self.write_keyword("CEIL");
17749        self.write("(");
17750        self.generate_expression(&f.this)?;
17751        // Handle Druid-style CEIL(time TO unit) syntax
17752        if let Some(to) = &f.to {
17753            self.write(" ");
17754            self.write_keyword("TO");
17755            self.write(" ");
17756            self.generate_expression(to)?;
17757        } else if let Some(decimals) = &f.decimals {
17758            self.write(", ");
17759            self.generate_expression(decimals)?;
17760        }
17761        self.write(")");
17762        Ok(())
17763    }
17764
17765    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17766        use crate::expressions::Literal;
17767
17768        if let Some(base) = &f.base {
17769            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17770            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17771            if self.is_log_base_none() {
17772                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17773                    self.write_func_name("LOG2");
17774                    self.write("(");
17775                    self.generate_expression(&f.this)?;
17776                    self.write(")");
17777                    return Ok(());
17778                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17779                    self.write_func_name("LOG10");
17780                    self.write("(");
17781                    self.generate_expression(&f.this)?;
17782                    self.write(")");
17783                    return Ok(());
17784                }
17785                // Other bases: fall through to LOG(base, value) — best effort
17786            }
17787
17788            self.write_func_name("LOG");
17789            self.write("(");
17790            if self.is_log_value_first() {
17791                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17792                self.generate_expression(&f.this)?;
17793                self.write(", ");
17794                self.generate_expression(base)?;
17795            } else {
17796                // Default (PostgreSQL, etc.): LOG(base, value)
17797                self.generate_expression(base)?;
17798                self.write(", ");
17799                self.generate_expression(&f.this)?;
17800            }
17801            self.write(")");
17802        } else {
17803            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17804            self.write_func_name("LOG");
17805            self.write("(");
17806            self.generate_expression(&f.this)?;
17807            self.write(")");
17808        }
17809        Ok(())
17810    }
17811
17812    /// Whether the target dialect uses LOG(value, base) order (value first).
17813    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17814    fn is_log_value_first(&self) -> bool {
17815        use crate::dialects::DialectType;
17816        matches!(
17817            self.config.dialect,
17818            Some(DialectType::BigQuery)
17819                | Some(DialectType::TSQL)
17820                | Some(DialectType::Tableau)
17821                | Some(DialectType::Fabric)
17822        )
17823    }
17824
17825    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17826    /// Presto, Trino, ClickHouse, Athena.
17827    fn is_log_base_none(&self) -> bool {
17828        use crate::dialects::DialectType;
17829        matches!(
17830            self.config.dialect,
17831            Some(DialectType::Presto)
17832                | Some(DialectType::Trino)
17833                | Some(DialectType::ClickHouse)
17834                | Some(DialectType::Athena)
17835        )
17836    }
17837
17838    // Date/time function generators
17839
17840    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17841        self.write_keyword("CURRENT_TIME");
17842        if let Some(precision) = f.precision {
17843            self.write(&format!("({})", precision));
17844        }
17845        Ok(())
17846    }
17847
17848    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17849        use crate::dialects::DialectType;
17850
17851        // Oracle/Redshift SYSDATE handling
17852        if f.sysdate {
17853            match self.config.dialect {
17854                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17855                    self.write_keyword("SYSDATE");
17856                    return Ok(());
17857                }
17858                Some(DialectType::Snowflake) => {
17859                    // Snowflake uses SYSDATE() function
17860                    self.write_keyword("SYSDATE");
17861                    self.write("()");
17862                    return Ok(());
17863                }
17864                _ => {
17865                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17866                }
17867            }
17868        }
17869
17870        self.write_keyword("CURRENT_TIMESTAMP");
17871        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17872        if let Some(precision) = f.precision {
17873            self.write(&format!("({})", precision));
17874        } else if matches!(
17875            self.config.dialect,
17876            Some(crate::dialects::DialectType::MySQL)
17877                | Some(crate::dialects::DialectType::SingleStore)
17878                | Some(crate::dialects::DialectType::TiDB)
17879                | Some(crate::dialects::DialectType::Spark)
17880                | Some(crate::dialects::DialectType::Hive)
17881                | Some(crate::dialects::DialectType::Databricks)
17882                | Some(crate::dialects::DialectType::ClickHouse)
17883                | Some(crate::dialects::DialectType::BigQuery)
17884                | Some(crate::dialects::DialectType::Snowflake)
17885        ) {
17886            self.write("()");
17887        }
17888        Ok(())
17889    }
17890
17891    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17892        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17893        if self.config.dialect == Some(DialectType::Exasol) {
17894            self.write_keyword("CONVERT_TZ");
17895            self.write("(");
17896            self.generate_expression(&f.this)?;
17897            self.write(", 'UTC', ");
17898            self.generate_expression(&f.zone)?;
17899            self.write(")");
17900            return Ok(());
17901        }
17902
17903        self.generate_expression(&f.this)?;
17904        self.write_space();
17905        self.write_keyword("AT TIME ZONE");
17906        self.write_space();
17907        self.generate_expression(&f.zone)?;
17908        Ok(())
17909    }
17910
17911    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17912        use crate::dialects::DialectType;
17913
17914        // Presto/Trino use DATE_ADD('unit', interval, date) format
17915        // with the interval cast to BIGINT when needed
17916        let is_presto_like = matches!(
17917            self.config.dialect,
17918            Some(DialectType::Presto) | Some(DialectType::Trino)
17919        );
17920
17921        if is_presto_like {
17922            self.write_keyword(name);
17923            self.write("(");
17924            // Unit as string literal
17925            self.write("'");
17926            self.write_simple_interval_unit(&f.unit, false);
17927            self.write("'");
17928            self.write(", ");
17929            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17930            let needs_cast = !self.returns_integer_type(&f.interval);
17931            if needs_cast {
17932                self.write_keyword("CAST");
17933                self.write("(");
17934            }
17935            self.generate_expression(&f.interval)?;
17936            if needs_cast {
17937                self.write_space();
17938                self.write_keyword("AS");
17939                self.write_space();
17940                self.write_keyword("BIGINT");
17941                self.write(")");
17942            }
17943            self.write(", ");
17944            self.generate_expression(&f.this)?;
17945            self.write(")");
17946        } else {
17947            self.write_keyword(name);
17948            self.write("(");
17949            self.generate_expression(&f.this)?;
17950            self.write(", ");
17951            self.write_keyword("INTERVAL");
17952            self.write_space();
17953            self.generate_expression(&f.interval)?;
17954            self.write_space();
17955            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17956            self.write(")");
17957        }
17958        Ok(())
17959    }
17960
17961    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17962    /// This is a heuristic to avoid full type inference
17963    fn returns_integer_type(&self, expr: &Expression) -> bool {
17964        use crate::expressions::{DataType, Literal};
17965        match expr {
17966            // Integer literals (no decimal point)
17967            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17968
17969            // FLOOR(x) returns integer if x is integer
17970            Expression::Floor(f) => self.returns_integer_type(&f.this),
17971
17972            // ROUND(x) returns integer if x is integer
17973            Expression::Round(f) => {
17974                // Only if no decimals arg or it's returning an integer
17975                f.decimals.is_none() && self.returns_integer_type(&f.this)
17976            }
17977
17978            // SIGN returns integer if input is integer
17979            Expression::Sign(f) => self.returns_integer_type(&f.this),
17980
17981            // ABS returns the same type as input
17982            Expression::Abs(f) => self.returns_integer_type(&f.this),
17983
17984            // Arithmetic operations on integers return integers
17985            Expression::Mul(op) => {
17986                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17987            }
17988            Expression::Add(op) => {
17989                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17990            }
17991            Expression::Sub(op) => {
17992                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17993            }
17994            Expression::Mod(op) => self.returns_integer_type(&op.left),
17995
17996            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17997            Expression::Cast(c) => matches!(
17998                &c.to,
17999                DataType::BigInt { .. }
18000                    | DataType::Int { .. }
18001                    | DataType::SmallInt { .. }
18002                    | DataType::TinyInt { .. }
18003            ),
18004
18005            // Negation: -x returns integer if x is integer
18006            Expression::Neg(op) => self.returns_integer_type(&op.this),
18007
18008            // Parenthesized expression
18009            Expression::Paren(p) => self.returns_integer_type(&p.this),
18010
18011            // Column references and most expressions are assumed to need casting
18012            // since we don't have full type information
18013            _ => false,
18014        }
18015    }
18016
18017    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18018        self.write_keyword("DATEDIFF");
18019        self.write("(");
18020        if let Some(unit) = &f.unit {
18021            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18022            self.write(", ");
18023        }
18024        self.generate_expression(&f.this)?;
18025        self.write(", ");
18026        self.generate_expression(&f.expression)?;
18027        self.write(")");
18028        Ok(())
18029    }
18030
18031    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18032        self.write_keyword("DATE_TRUNC");
18033        self.write("('");
18034        self.write_datetime_field(&f.unit);
18035        self.write("', ");
18036        self.generate_expression(&f.this)?;
18037        self.write(")");
18038        Ok(())
18039    }
18040
18041    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18042        use crate::dialects::DialectType;
18043        use crate::expressions::DateTimeField;
18044
18045        self.write_keyword("LAST_DAY");
18046        self.write("(");
18047        self.generate_expression(&f.this)?;
18048        if let Some(unit) = &f.unit {
18049            self.write(", ");
18050            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18051            // WEEK(SUNDAY) -> WEEK
18052            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18053                if let DateTimeField::WeekWithModifier(_) = unit {
18054                    self.write_keyword("WEEK");
18055                } else {
18056                    self.write_datetime_field(unit);
18057                }
18058            } else {
18059                self.write_datetime_field(unit);
18060            }
18061        }
18062        self.write(")");
18063        Ok(())
18064    }
18065
18066    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18067        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18068        if matches!(
18069            self.config.dialect,
18070            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18071        ) {
18072            self.write_keyword("DATEPART");
18073            self.write("(");
18074            self.write_datetime_field(&f.field);
18075            self.write(", ");
18076            self.generate_expression(&f.this)?;
18077            self.write(")");
18078            return Ok(());
18079        }
18080        self.write_keyword("EXTRACT");
18081        self.write("(");
18082        // Hive/Spark use lowercase datetime fields in EXTRACT
18083        if matches!(
18084            self.config.dialect,
18085            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18086        ) {
18087            self.write_datetime_field_lower(&f.field);
18088        } else {
18089            self.write_datetime_field(&f.field);
18090        }
18091        self.write_space();
18092        self.write_keyword("FROM");
18093        self.write_space();
18094        self.generate_expression(&f.this)?;
18095        self.write(")");
18096        Ok(())
18097    }
18098
18099    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18100        self.write_keyword("TO_DATE");
18101        self.write("(");
18102        self.generate_expression(&f.this)?;
18103        if let Some(format) = &f.format {
18104            self.write(", ");
18105            self.generate_expression(format)?;
18106        }
18107        self.write(")");
18108        Ok(())
18109    }
18110
18111    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18112        self.write_keyword("TO_TIMESTAMP");
18113        self.write("(");
18114        self.generate_expression(&f.this)?;
18115        if let Some(format) = &f.format {
18116            self.write(", ");
18117            self.generate_expression(format)?;
18118        }
18119        self.write(")");
18120        Ok(())
18121    }
18122
18123    // Control flow function generators
18124
18125    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18126        use crate::dialects::DialectType;
18127
18128        // Generic mode: normalize IF to CASE WHEN
18129        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18130            self.write_keyword("CASE WHEN");
18131            self.write_space();
18132            self.generate_expression(&f.condition)?;
18133            self.write_space();
18134            self.write_keyword("THEN");
18135            self.write_space();
18136            self.generate_expression(&f.true_value)?;
18137            if let Some(false_val) = &f.false_value {
18138                self.write_space();
18139                self.write_keyword("ELSE");
18140                self.write_space();
18141                self.generate_expression(false_val)?;
18142            }
18143            self.write_space();
18144            self.write_keyword("END");
18145            return Ok(());
18146        }
18147
18148        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18149        if self.config.dialect == Some(DialectType::Exasol) {
18150            self.write_keyword("IF");
18151            self.write_space();
18152            self.generate_expression(&f.condition)?;
18153            self.write_space();
18154            self.write_keyword("THEN");
18155            self.write_space();
18156            self.generate_expression(&f.true_value)?;
18157            if let Some(false_val) = &f.false_value {
18158                self.write_space();
18159                self.write_keyword("ELSE");
18160                self.write_space();
18161                self.generate_expression(false_val)?;
18162            }
18163            self.write_space();
18164            self.write_keyword("ENDIF");
18165            return Ok(());
18166        }
18167
18168        // Choose function name based on target dialect
18169        let func_name = match self.config.dialect {
18170            Some(DialectType::Snowflake) => "IFF",
18171            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18172            Some(DialectType::Drill) => "`IF`",
18173            _ => "IF",
18174        };
18175        self.write(func_name);
18176        self.write("(");
18177        self.generate_expression(&f.condition)?;
18178        self.write(", ");
18179        self.generate_expression(&f.true_value)?;
18180        if let Some(false_val) = &f.false_value {
18181            self.write(", ");
18182            self.generate_expression(false_val)?;
18183        }
18184        self.write(")");
18185        Ok(())
18186    }
18187
18188    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18189        self.write_keyword("NVL2");
18190        self.write("(");
18191        self.generate_expression(&f.this)?;
18192        self.write(", ");
18193        self.generate_expression(&f.true_value)?;
18194        self.write(", ");
18195        self.generate_expression(&f.false_value)?;
18196        self.write(")");
18197        Ok(())
18198    }
18199
18200    // Typed aggregate function generators
18201
18202    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18203        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18204        let count_name = match self.config.normalize_functions {
18205            NormalizeFunctions::Upper => "COUNT".to_string(),
18206            NormalizeFunctions::Lower => "count".to_string(),
18207            NormalizeFunctions::None => f
18208                .original_name
18209                .clone()
18210                .unwrap_or_else(|| "COUNT".to_string()),
18211        };
18212        self.write(&count_name);
18213        self.write("(");
18214        if f.distinct {
18215            self.write_keyword("DISTINCT");
18216            self.write_space();
18217        }
18218        if f.star {
18219            self.write("*");
18220        } else if let Some(ref expr) = f.this {
18221            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18222            if let Expression::Tuple(tuple) = expr {
18223                // Check if we need to transform multi-arg COUNT DISTINCT
18224                // When dialect doesn't support multi_arg_distinct, transform:
18225                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18226                let needs_transform =
18227                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18228
18229                if needs_transform {
18230                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18231                    self.write_keyword("CASE");
18232                    for e in &tuple.expressions {
18233                        self.write_space();
18234                        self.write_keyword("WHEN");
18235                        self.write_space();
18236                        self.generate_expression(e)?;
18237                        self.write_space();
18238                        self.write_keyword("IS NULL THEN NULL");
18239                    }
18240                    self.write_space();
18241                    self.write_keyword("ELSE");
18242                    self.write(" (");
18243                    for (i, e) in tuple.expressions.iter().enumerate() {
18244                        if i > 0 {
18245                            self.write(", ");
18246                        }
18247                        self.generate_expression(e)?;
18248                    }
18249                    self.write(")");
18250                    self.write_space();
18251                    self.write_keyword("END");
18252                } else {
18253                    for (i, e) in tuple.expressions.iter().enumerate() {
18254                        if i > 0 {
18255                            self.write(", ");
18256                        }
18257                        self.generate_expression(e)?;
18258                    }
18259                }
18260            } else {
18261                self.generate_expression(expr)?;
18262            }
18263        }
18264        // RESPECT NULLS / IGNORE NULLS
18265        if let Some(ignore) = f.ignore_nulls {
18266            self.write_space();
18267            if ignore {
18268                self.write_keyword("IGNORE NULLS");
18269            } else {
18270                self.write_keyword("RESPECT NULLS");
18271            }
18272        }
18273        self.write(")");
18274        if let Some(ref filter) = f.filter {
18275            self.write_space();
18276            self.write_keyword("FILTER");
18277            self.write("(");
18278            self.write_keyword("WHERE");
18279            self.write_space();
18280            self.generate_expression(filter)?;
18281            self.write(")");
18282        }
18283        Ok(())
18284    }
18285
18286    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18287        // Apply function name normalization based on config
18288        let func_name = match self.config.normalize_functions {
18289            NormalizeFunctions::Upper => name.to_uppercase(),
18290            NormalizeFunctions::Lower => name.to_lowercase(),
18291            NormalizeFunctions::None => {
18292                // Use the original function name from parsing if available,
18293                // otherwise fall back to lowercase of the hardcoded constant
18294                if let Some(ref original) = f.name {
18295                    original.clone()
18296                } else {
18297                    name.to_lowercase()
18298                }
18299            }
18300        };
18301        self.write(&func_name);
18302        self.write("(");
18303        if f.distinct {
18304            self.write_keyword("DISTINCT");
18305            self.write_space();
18306        }
18307        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18308        if !matches!(f.this, Expression::Null(_)) {
18309            self.generate_expression(&f.this)?;
18310        }
18311        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18312        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18313        if self.config.ignore_nulls_in_func
18314            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18315        {
18316            match f.ignore_nulls {
18317                Some(true) => {
18318                    self.write_space();
18319                    self.write_keyword("IGNORE NULLS");
18320                }
18321                Some(false) => {
18322                    self.write_space();
18323                    self.write_keyword("RESPECT NULLS");
18324                }
18325                None => {}
18326            }
18327        }
18328        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18329        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18330        if let Some((ref expr, is_max)) = f.having_max {
18331            self.write_space();
18332            self.write_keyword("HAVING");
18333            self.write_space();
18334            if is_max {
18335                self.write_keyword("MAX");
18336            } else {
18337                self.write_keyword("MIN");
18338            }
18339            self.write_space();
18340            self.generate_expression(expr)?;
18341        }
18342        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18343        if !f.order_by.is_empty() {
18344            self.write_space();
18345            self.write_keyword("ORDER BY");
18346            self.write_space();
18347            for (i, ord) in f.order_by.iter().enumerate() {
18348                if i > 0 {
18349                    self.write(", ");
18350                }
18351                self.generate_ordered(ord)?;
18352            }
18353        }
18354        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18355        if let Some(ref limit) = f.limit {
18356            self.write_space();
18357            self.write_keyword("LIMIT");
18358            self.write_space();
18359            // Check if this is a Tuple representing LIMIT offset, count
18360            if let Expression::Tuple(t) = limit.as_ref() {
18361                if t.expressions.len() == 2 {
18362                    self.generate_expression(&t.expressions[0])?;
18363                    self.write(", ");
18364                    self.generate_expression(&t.expressions[1])?;
18365                } else {
18366                    self.generate_expression(limit)?;
18367                }
18368            } else {
18369                self.generate_expression(limit)?;
18370            }
18371        }
18372        self.write(")");
18373        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18374        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18375        if !self.config.ignore_nulls_in_func
18376            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18377        {
18378            match f.ignore_nulls {
18379                Some(true) => {
18380                    self.write_space();
18381                    self.write_keyword("IGNORE NULLS");
18382                }
18383                Some(false) => {
18384                    self.write_space();
18385                    self.write_keyword("RESPECT NULLS");
18386                }
18387                None => {}
18388            }
18389        }
18390        if let Some(ref filter) = f.filter {
18391            self.write_space();
18392            self.write_keyword("FILTER");
18393            self.write("(");
18394            self.write_keyword("WHERE");
18395            self.write_space();
18396            self.generate_expression(filter)?;
18397            self.write(")");
18398        }
18399        Ok(())
18400    }
18401
18402    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18403        self.write_keyword("GROUP_CONCAT");
18404        self.write("(");
18405        if f.distinct {
18406            self.write_keyword("DISTINCT");
18407            self.write_space();
18408        }
18409        self.generate_expression(&f.this)?;
18410        if let Some(ref order_by) = f.order_by {
18411            self.write_space();
18412            self.write_keyword("ORDER BY");
18413            self.write_space();
18414            for (i, ord) in order_by.iter().enumerate() {
18415                if i > 0 {
18416                    self.write(", ");
18417                }
18418                self.generate_ordered(ord)?;
18419            }
18420        }
18421        if let Some(ref sep) = f.separator {
18422            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18423            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18424            if matches!(
18425                self.config.dialect,
18426                Some(crate::dialects::DialectType::SQLite)
18427            ) {
18428                self.write(", ");
18429                self.generate_expression(sep)?;
18430            } else {
18431                self.write_space();
18432                self.write_keyword("SEPARATOR");
18433                self.write_space();
18434                self.generate_expression(sep)?;
18435            }
18436        }
18437        self.write(")");
18438        if let Some(ref filter) = f.filter {
18439            self.write_space();
18440            self.write_keyword("FILTER");
18441            self.write("(");
18442            self.write_keyword("WHERE");
18443            self.write_space();
18444            self.generate_expression(filter)?;
18445            self.write(")");
18446        }
18447        Ok(())
18448    }
18449
18450    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18451        let is_tsql = matches!(
18452            self.config.dialect,
18453            Some(crate::dialects::DialectType::TSQL)
18454        );
18455        self.write_keyword("STRING_AGG");
18456        self.write("(");
18457        if f.distinct {
18458            self.write_keyword("DISTINCT");
18459            self.write_space();
18460        }
18461        self.generate_expression(&f.this)?;
18462        if let Some(ref separator) = f.separator {
18463            self.write(", ");
18464            self.generate_expression(separator)?;
18465        }
18466        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18467        if !is_tsql {
18468            if let Some(ref order_by) = f.order_by {
18469                self.write_space();
18470                self.write_keyword("ORDER BY");
18471                self.write_space();
18472                for (i, ord) in order_by.iter().enumerate() {
18473                    if i > 0 {
18474                        self.write(", ");
18475                    }
18476                    self.generate_ordered(ord)?;
18477                }
18478            }
18479        }
18480        if let Some(ref limit) = f.limit {
18481            self.write_space();
18482            self.write_keyword("LIMIT");
18483            self.write_space();
18484            self.generate_expression(limit)?;
18485        }
18486        self.write(")");
18487        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18488        if is_tsql {
18489            if let Some(ref order_by) = f.order_by {
18490                self.write_space();
18491                self.write_keyword("WITHIN GROUP");
18492                self.write(" (");
18493                self.write_keyword("ORDER BY");
18494                self.write_space();
18495                for (i, ord) in order_by.iter().enumerate() {
18496                    if i > 0 {
18497                        self.write(", ");
18498                    }
18499                    self.generate_ordered(ord)?;
18500                }
18501                self.write(")");
18502            }
18503        }
18504        if let Some(ref filter) = f.filter {
18505            self.write_space();
18506            self.write_keyword("FILTER");
18507            self.write("(");
18508            self.write_keyword("WHERE");
18509            self.write_space();
18510            self.generate_expression(filter)?;
18511            self.write(")");
18512        }
18513        Ok(())
18514    }
18515
18516    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18517        use crate::dialects::DialectType;
18518        self.write_keyword("LISTAGG");
18519        self.write("(");
18520        if f.distinct {
18521            self.write_keyword("DISTINCT");
18522            self.write_space();
18523        }
18524        self.generate_expression(&f.this)?;
18525        if let Some(ref sep) = f.separator {
18526            self.write(", ");
18527            self.generate_expression(sep)?;
18528        } else if matches!(
18529            self.config.dialect,
18530            Some(DialectType::Trino) | Some(DialectType::Presto)
18531        ) {
18532            // Trino/Presto require explicit separator; default to ','
18533            self.write(", ','");
18534        }
18535        if let Some(ref overflow) = f.on_overflow {
18536            self.write_space();
18537            self.write_keyword("ON OVERFLOW");
18538            self.write_space();
18539            match overflow {
18540                ListAggOverflow::Error => self.write_keyword("ERROR"),
18541                ListAggOverflow::Truncate { filler, with_count } => {
18542                    self.write_keyword("TRUNCATE");
18543                    if let Some(ref fill) = filler {
18544                        self.write_space();
18545                        self.generate_expression(fill)?;
18546                    }
18547                    if *with_count {
18548                        self.write_space();
18549                        self.write_keyword("WITH COUNT");
18550                    } else {
18551                        self.write_space();
18552                        self.write_keyword("WITHOUT COUNT");
18553                    }
18554                }
18555            }
18556        }
18557        self.write(")");
18558        if let Some(ref order_by) = f.order_by {
18559            self.write_space();
18560            self.write_keyword("WITHIN GROUP");
18561            self.write(" (");
18562            self.write_keyword("ORDER BY");
18563            self.write_space();
18564            for (i, ord) in order_by.iter().enumerate() {
18565                if i > 0 {
18566                    self.write(", ");
18567                }
18568                self.generate_ordered(ord)?;
18569            }
18570            self.write(")");
18571        }
18572        if let Some(ref filter) = f.filter {
18573            self.write_space();
18574            self.write_keyword("FILTER");
18575            self.write("(");
18576            self.write_keyword("WHERE");
18577            self.write_space();
18578            self.generate_expression(filter)?;
18579            self.write(")");
18580        }
18581        Ok(())
18582    }
18583
18584    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18585        self.write_keyword("SUM_IF");
18586        self.write("(");
18587        self.generate_expression(&f.this)?;
18588        self.write(", ");
18589        self.generate_expression(&f.condition)?;
18590        self.write(")");
18591        if let Some(ref filter) = f.filter {
18592            self.write_space();
18593            self.write_keyword("FILTER");
18594            self.write("(");
18595            self.write_keyword("WHERE");
18596            self.write_space();
18597            self.generate_expression(filter)?;
18598            self.write(")");
18599        }
18600        Ok(())
18601    }
18602
18603    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18604        self.write_keyword("APPROX_PERCENTILE");
18605        self.write("(");
18606        self.generate_expression(&f.this)?;
18607        self.write(", ");
18608        self.generate_expression(&f.percentile)?;
18609        if let Some(ref acc) = f.accuracy {
18610            self.write(", ");
18611            self.generate_expression(acc)?;
18612        }
18613        self.write(")");
18614        if let Some(ref filter) = f.filter {
18615            self.write_space();
18616            self.write_keyword("FILTER");
18617            self.write("(");
18618            self.write_keyword("WHERE");
18619            self.write_space();
18620            self.generate_expression(filter)?;
18621            self.write(")");
18622        }
18623        Ok(())
18624    }
18625
18626    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18627        self.write_keyword(name);
18628        self.write("(");
18629        self.generate_expression(&f.percentile)?;
18630        self.write(")");
18631        if let Some(ref order_by) = f.order_by {
18632            self.write_space();
18633            self.write_keyword("WITHIN GROUP");
18634            self.write(" (");
18635            self.write_keyword("ORDER BY");
18636            self.write_space();
18637            self.generate_expression(&f.this)?;
18638            for ord in order_by.iter() {
18639                if ord.desc {
18640                    self.write_space();
18641                    self.write_keyword("DESC");
18642                }
18643            }
18644            self.write(")");
18645        }
18646        if let Some(ref filter) = f.filter {
18647            self.write_space();
18648            self.write_keyword("FILTER");
18649            self.write("(");
18650            self.write_keyword("WHERE");
18651            self.write_space();
18652            self.generate_expression(filter)?;
18653            self.write(")");
18654        }
18655        Ok(())
18656    }
18657
18658    // Window function generators
18659
18660    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18661        self.write_keyword("NTILE");
18662        self.write("(");
18663        if let Some(num_buckets) = &f.num_buckets {
18664            self.generate_expression(num_buckets)?;
18665        }
18666        if let Some(order_by) = &f.order_by {
18667            self.write_keyword(" ORDER BY ");
18668            for (i, ob) in order_by.iter().enumerate() {
18669                if i > 0 {
18670                    self.write(", ");
18671                }
18672                self.generate_ordered(ob)?;
18673            }
18674        }
18675        self.write(")");
18676        Ok(())
18677    }
18678
18679    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18680        self.write_keyword(name);
18681        self.write("(");
18682        self.generate_expression(&f.this)?;
18683        if let Some(ref offset) = f.offset {
18684            self.write(", ");
18685            self.generate_expression(offset)?;
18686            if let Some(ref default) = f.default {
18687                self.write(", ");
18688                self.generate_expression(default)?;
18689            }
18690        }
18691        // IGNORE NULLS inside parens for dialects like BigQuery
18692        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18693            self.write_space();
18694            self.write_keyword("IGNORE NULLS");
18695        }
18696        self.write(")");
18697        // IGNORE NULLS outside parens for other dialects
18698        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18699            self.write_space();
18700            self.write_keyword("IGNORE NULLS");
18701        }
18702        Ok(())
18703    }
18704
18705    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18706        self.write_keyword(name);
18707        self.write("(");
18708        self.generate_expression(&f.this)?;
18709        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18710        if self.config.ignore_nulls_in_func {
18711            match f.ignore_nulls {
18712                Some(true) => {
18713                    self.write_space();
18714                    self.write_keyword("IGNORE NULLS");
18715                }
18716                Some(false) => {
18717                    self.write_space();
18718                    self.write_keyword("RESPECT NULLS");
18719                }
18720                None => {}
18721            }
18722        }
18723        self.write(")");
18724        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18725        if !self.config.ignore_nulls_in_func {
18726            match f.ignore_nulls {
18727                Some(true) => {
18728                    self.write_space();
18729                    self.write_keyword("IGNORE NULLS");
18730                }
18731                Some(false) => {
18732                    self.write_space();
18733                    self.write_keyword("RESPECT NULLS");
18734                }
18735                None => {}
18736            }
18737        }
18738        Ok(())
18739    }
18740
18741    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18742        self.write_keyword("NTH_VALUE");
18743        self.write("(");
18744        self.generate_expression(&f.this)?;
18745        self.write(", ");
18746        self.generate_expression(&f.offset)?;
18747        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18748        if self.config.ignore_nulls_in_func {
18749            match f.ignore_nulls {
18750                Some(true) => {
18751                    self.write_space();
18752                    self.write_keyword("IGNORE NULLS");
18753                }
18754                Some(false) => {
18755                    self.write_space();
18756                    self.write_keyword("RESPECT NULLS");
18757                }
18758                None => {}
18759            }
18760        }
18761        self.write(")");
18762        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18763        if matches!(
18764            self.config.dialect,
18765            Some(crate::dialects::DialectType::Snowflake)
18766        ) {
18767            match f.from_first {
18768                Some(true) => {
18769                    self.write_space();
18770                    self.write_keyword("FROM FIRST");
18771                }
18772                Some(false) => {
18773                    self.write_space();
18774                    self.write_keyword("FROM LAST");
18775                }
18776                None => {}
18777            }
18778        }
18779        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18780        if !self.config.ignore_nulls_in_func {
18781            match f.ignore_nulls {
18782                Some(true) => {
18783                    self.write_space();
18784                    self.write_keyword("IGNORE NULLS");
18785                }
18786                Some(false) => {
18787                    self.write_space();
18788                    self.write_keyword("RESPECT NULLS");
18789                }
18790                None => {}
18791            }
18792        }
18793        Ok(())
18794    }
18795
18796    // Additional string function generators
18797
18798    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18799        // Standard syntax: POSITION(substr IN str)
18800        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18801        if matches!(
18802            self.config.dialect,
18803            Some(crate::dialects::DialectType::ClickHouse)
18804        ) {
18805            self.write_keyword("POSITION");
18806            self.write("(");
18807            self.generate_expression(&f.string)?;
18808            self.write(", ");
18809            self.generate_expression(&f.substring)?;
18810            if let Some(ref start) = f.start {
18811                self.write(", ");
18812                self.generate_expression(start)?;
18813            }
18814            self.write(")");
18815            return Ok(());
18816        }
18817
18818        self.write_keyword("POSITION");
18819        self.write("(");
18820        self.generate_expression(&f.substring)?;
18821        self.write_space();
18822        self.write_keyword("IN");
18823        self.write_space();
18824        self.generate_expression(&f.string)?;
18825        if let Some(ref start) = f.start {
18826            self.write(", ");
18827            self.generate_expression(start)?;
18828        }
18829        self.write(")");
18830        Ok(())
18831    }
18832
18833    // Additional math function generators
18834
18835    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18836        // Teradata RANDOM(lower, upper)
18837        if f.lower.is_some() || f.upper.is_some() {
18838            self.write_keyword("RANDOM");
18839            self.write("(");
18840            if let Some(ref lower) = f.lower {
18841                self.generate_expression(lower)?;
18842            }
18843            if let Some(ref upper) = f.upper {
18844                self.write(", ");
18845                self.generate_expression(upper)?;
18846            }
18847            self.write(")");
18848            return Ok(());
18849        }
18850        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18851        let func_name = match self.config.dialect {
18852            Some(crate::dialects::DialectType::Snowflake)
18853            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18854            _ => "RAND",
18855        };
18856        self.write_keyword(func_name);
18857        self.write("(");
18858        // DuckDB doesn't support seeded RANDOM, so skip the seed
18859        if !matches!(
18860            self.config.dialect,
18861            Some(crate::dialects::DialectType::DuckDB)
18862        ) {
18863            if let Some(ref seed) = f.seed {
18864                self.generate_expression(seed)?;
18865            }
18866        }
18867        self.write(")");
18868        Ok(())
18869    }
18870
18871    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18872        self.write_keyword("TRUNCATE");
18873        self.write("(");
18874        self.generate_expression(&f.this)?;
18875        if let Some(ref decimals) = f.decimals {
18876            self.write(", ");
18877            self.generate_expression(decimals)?;
18878        }
18879        self.write(")");
18880        Ok(())
18881    }
18882
18883    // Control flow generators
18884
18885    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18886        self.write_keyword("DECODE");
18887        self.write("(");
18888        self.generate_expression(&f.this)?;
18889        for (search, result) in &f.search_results {
18890            self.write(", ");
18891            self.generate_expression(search)?;
18892            self.write(", ");
18893            self.generate_expression(result)?;
18894        }
18895        if let Some(ref default) = f.default {
18896            self.write(", ");
18897            self.generate_expression(default)?;
18898        }
18899        self.write(")");
18900        Ok(())
18901    }
18902
18903    // Date/time function generators
18904
18905    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18906        self.write_keyword(name);
18907        self.write("(");
18908        self.generate_expression(&f.this)?;
18909        self.write(", ");
18910        self.generate_expression(&f.format)?;
18911        self.write(")");
18912        Ok(())
18913    }
18914
18915    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18916        self.write_keyword("FROM_UNIXTIME");
18917        self.write("(");
18918        self.generate_expression(&f.this)?;
18919        if let Some(ref format) = f.format {
18920            self.write(", ");
18921            self.generate_expression(format)?;
18922        }
18923        self.write(")");
18924        Ok(())
18925    }
18926
18927    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18928        self.write_keyword("UNIX_TIMESTAMP");
18929        self.write("(");
18930        if let Some(ref expr) = f.this {
18931            self.generate_expression(expr)?;
18932            if let Some(ref format) = f.format {
18933                self.write(", ");
18934                self.generate_expression(format)?;
18935            }
18936        } else if matches!(
18937            self.config.dialect,
18938            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18939        ) {
18940            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18941            self.write_keyword("CURRENT_TIMESTAMP");
18942            self.write("()");
18943        }
18944        self.write(")");
18945        Ok(())
18946    }
18947
18948    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18949        self.write_keyword("MAKE_DATE");
18950        self.write("(");
18951        self.generate_expression(&f.year)?;
18952        self.write(", ");
18953        self.generate_expression(&f.month)?;
18954        self.write(", ");
18955        self.generate_expression(&f.day)?;
18956        self.write(")");
18957        Ok(())
18958    }
18959
18960    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18961        self.write_keyword("MAKE_TIMESTAMP");
18962        self.write("(");
18963        self.generate_expression(&f.year)?;
18964        self.write(", ");
18965        self.generate_expression(&f.month)?;
18966        self.write(", ");
18967        self.generate_expression(&f.day)?;
18968        self.write(", ");
18969        self.generate_expression(&f.hour)?;
18970        self.write(", ");
18971        self.generate_expression(&f.minute)?;
18972        self.write(", ");
18973        self.generate_expression(&f.second)?;
18974        if let Some(ref tz) = f.timezone {
18975            self.write(", ");
18976            self.generate_expression(tz)?;
18977        }
18978        self.write(")");
18979        Ok(())
18980    }
18981
18982    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18983    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18984        match expr {
18985            Expression::Struct(s) => {
18986                if s.fields.iter().all(|(name, _)| name.is_some()) {
18987                    Some(
18988                        s.fields
18989                            .iter()
18990                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18991                            .collect(),
18992                    )
18993                } else {
18994                    None
18995                }
18996            }
18997            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18998                // Check if all args are Alias (named fields)
18999                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
19000                    Some(
19001                        f.args
19002                            .iter()
19003                            .filter_map(|a| {
19004                                if let Expression::Alias(alias) = a {
19005                                    Some(alias.alias.name.clone())
19006                                } else {
19007                                    None
19008                                }
19009                            })
19010                            .collect(),
19011                    )
19012                } else {
19013                    None
19014                }
19015            }
19016            _ => None,
19017        }
19018    }
19019
19020    /// Check if a struct expression has any unnamed fields
19021    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
19022        match expr {
19023            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
19024            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19025                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
19026            }
19027            _ => false,
19028        }
19029    }
19030
19031    /// Get the field count of a struct expression
19032    fn struct_field_count(expr: &Expression) -> usize {
19033        match expr {
19034            Expression::Struct(s) => s.fields.len(),
19035            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
19036            _ => 0,
19037        }
19038    }
19039
19040    /// Apply field names to an unnamed struct expression, producing a new expression with names
19041    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
19042        match expr {
19043            Expression::Struct(s) => {
19044                let mut new_fields = Vec::with_capacity(s.fields.len());
19045                for (i, (name, value)) in s.fields.iter().enumerate() {
19046                    if name.is_none() && i < field_names.len() {
19047                        new_fields.push((Some(field_names[i].clone()), value.clone()));
19048                    } else {
19049                        new_fields.push((name.clone(), value.clone()));
19050                    }
19051                }
19052                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
19053            }
19054            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19055                let mut new_args = Vec::with_capacity(f.args.len());
19056                for (i, arg) in f.args.iter().enumerate() {
19057                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
19058                        // Wrap the value in an Alias with the inherited name
19059                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19060                            this: arg.clone(),
19061                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19062                            column_aliases: Vec::new(),
19063                            pre_alias_comments: Vec::new(),
19064                            trailing_comments: Vec::new(),
19065                            inferred_type: None,
19066                        })));
19067                    } else {
19068                        new_args.push(arg.clone());
19069                    }
19070                }
19071                Expression::Function(Box::new(crate::expressions::Function {
19072                    name: f.name.clone(),
19073                    args: new_args,
19074                    distinct: f.distinct,
19075                    trailing_comments: f.trailing_comments.clone(),
19076                    use_bracket_syntax: f.use_bracket_syntax,
19077                    no_parens: f.no_parens,
19078                    quoted: f.quoted,
19079                    span: None,
19080                    inferred_type: None,
19081                }))
19082            }
19083            _ => expr.clone(),
19084        }
19085    }
19086
19087    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19088    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19089    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19090    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19091        let first = match expressions.first() {
19092            Some(e) => e,
19093            None => return expressions.to_vec(),
19094        };
19095
19096        let field_names = match Self::extract_struct_field_names(first) {
19097            Some(names) if !names.is_empty() => names,
19098            _ => return expressions.to_vec(),
19099        };
19100
19101        let mut result = Vec::with_capacity(expressions.len());
19102        for (idx, expr) in expressions.iter().enumerate() {
19103            if idx == 0 {
19104                result.push(expr.clone());
19105                continue;
19106            }
19107            // Check if this is a struct with unnamed fields that needs name propagation
19108            if Self::struct_field_count(expr) == field_names.len()
19109                && Self::struct_has_unnamed_fields(expr)
19110            {
19111                result.push(Self::apply_struct_field_names(expr, &field_names));
19112            } else {
19113                result.push(expr.clone());
19114            }
19115        }
19116        result
19117    }
19118
19119    // Array function generators
19120
19121    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19122        // Apply struct name inheritance for target dialects that need it
19123        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19124        let needs_inheritance = matches!(
19125            self.config.dialect,
19126            Some(DialectType::DuckDB)
19127                | Some(DialectType::Spark)
19128                | Some(DialectType::Databricks)
19129                | Some(DialectType::Hive)
19130                | Some(DialectType::Snowflake)
19131                | Some(DialectType::Presto)
19132                | Some(DialectType::Trino)
19133        );
19134        let propagated: Vec<Expression>;
19135        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19136            propagated = Self::inherit_struct_field_names(&f.expressions);
19137            &propagated
19138        } else {
19139            &f.expressions
19140        };
19141
19142        // Check if elements should be split onto multiple lines (pretty + too wide)
19143        let should_split = if self.config.pretty && !expressions.is_empty() {
19144            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19145            for expr in expressions {
19146                let mut temp_gen = Generator::with_config(self.config.clone());
19147                temp_gen.config.pretty = false;
19148                temp_gen.generate_expression(expr)?;
19149                expr_strings.push(temp_gen.output);
19150            }
19151            self.too_wide(&expr_strings)
19152        } else {
19153            false
19154        };
19155
19156        if f.bracket_notation {
19157            // For Spark/Databricks, use ARRAY(...) with parens
19158            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19159            // For others (DuckDB, Snowflake), use bare [...]
19160            let (open, close) = match self.config.dialect {
19161                None
19162                | Some(DialectType::Generic)
19163                | Some(DialectType::Spark)
19164                | Some(DialectType::Databricks)
19165                | Some(DialectType::Hive) => {
19166                    self.write_keyword("ARRAY");
19167                    ("(", ")")
19168                }
19169                Some(DialectType::Presto)
19170                | Some(DialectType::Trino)
19171                | Some(DialectType::PostgreSQL)
19172                | Some(DialectType::Redshift)
19173                | Some(DialectType::Materialize)
19174                | Some(DialectType::RisingWave)
19175                | Some(DialectType::CockroachDB) => {
19176                    self.write_keyword("ARRAY");
19177                    ("[", "]")
19178                }
19179                _ => ("[", "]"),
19180            };
19181            self.write(open);
19182            if should_split {
19183                self.write_newline();
19184                self.indent_level += 1;
19185                for (i, expr) in expressions.iter().enumerate() {
19186                    self.write_indent();
19187                    self.generate_expression(expr)?;
19188                    if i + 1 < expressions.len() {
19189                        self.write(",");
19190                    }
19191                    self.write_newline();
19192                }
19193                self.indent_level -= 1;
19194                self.write_indent();
19195            } else {
19196                for (i, expr) in expressions.iter().enumerate() {
19197                    if i > 0 {
19198                        self.write(", ");
19199                    }
19200                    self.generate_expression(expr)?;
19201                }
19202            }
19203            self.write(close);
19204        } else {
19205            // Use LIST keyword if that was the original syntax (DuckDB)
19206            if f.use_list_keyword {
19207                self.write_keyword("LIST");
19208            } else {
19209                self.write_keyword("ARRAY");
19210            }
19211            // For Spark/Hive, always use ARRAY(...) with parens
19212            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19213            let has_subquery = expressions
19214                .iter()
19215                .any(|e| matches!(e, Expression::Select(_)));
19216            let (open, close) = if matches!(
19217                self.config.dialect,
19218                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19219            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19220                && has_subquery)
19221            {
19222                ("(", ")")
19223            } else {
19224                ("[", "]")
19225            };
19226            self.write(open);
19227            if should_split {
19228                self.write_newline();
19229                self.indent_level += 1;
19230                for (i, expr) in expressions.iter().enumerate() {
19231                    self.write_indent();
19232                    self.generate_expression(expr)?;
19233                    if i + 1 < expressions.len() {
19234                        self.write(",");
19235                    }
19236                    self.write_newline();
19237                }
19238                self.indent_level -= 1;
19239                self.write_indent();
19240            } else {
19241                for (i, expr) in expressions.iter().enumerate() {
19242                    if i > 0 {
19243                        self.write(", ");
19244                    }
19245                    self.generate_expression(expr)?;
19246                }
19247            }
19248            self.write(close);
19249        }
19250        Ok(())
19251    }
19252
19253    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19254        self.write_keyword("ARRAY_SORT");
19255        self.write("(");
19256        self.generate_expression(&f.this)?;
19257        if let Some(ref comp) = f.comparator {
19258            self.write(", ");
19259            self.generate_expression(comp)?;
19260        }
19261        self.write(")");
19262        Ok(())
19263    }
19264
19265    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19266        self.write_keyword(name);
19267        self.write("(");
19268        self.generate_expression(&f.this)?;
19269        self.write(", ");
19270        self.generate_expression(&f.separator)?;
19271        if let Some(ref null_rep) = f.null_replacement {
19272            self.write(", ");
19273            self.generate_expression(null_rep)?;
19274        }
19275        self.write(")");
19276        Ok(())
19277    }
19278
19279    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19280        self.write_keyword("UNNEST");
19281        self.write("(");
19282        self.generate_expression(&f.this)?;
19283        for extra in &f.expressions {
19284            self.write(", ");
19285            self.generate_expression(extra)?;
19286        }
19287        self.write(")");
19288        if f.with_ordinality {
19289            self.write_space();
19290            if self.config.unnest_with_ordinality {
19291                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19292                self.write_keyword("WITH ORDINALITY");
19293            } else if f.offset_alias.is_some() {
19294                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19295                // Alias (if any) comes BEFORE WITH OFFSET
19296                if let Some(ref alias) = f.alias {
19297                    self.write_keyword("AS");
19298                    self.write_space();
19299                    self.generate_identifier(alias)?;
19300                    self.write_space();
19301                }
19302                self.write_keyword("WITH OFFSET");
19303                if let Some(ref offset_alias) = f.offset_alias {
19304                    self.write_space();
19305                    self.write_keyword("AS");
19306                    self.write_space();
19307                    self.generate_identifier(offset_alias)?;
19308                }
19309            } else {
19310                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19311                self.write_keyword("WITH OFFSET");
19312                if f.alias.is_none() {
19313                    self.write(" AS offset");
19314                }
19315            }
19316        }
19317        if let Some(ref alias) = f.alias {
19318            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19319            let should_add_alias = if !f.with_ordinality {
19320                true
19321            } else if self.config.unnest_with_ordinality {
19322                // Presto/Trino: alias comes after WITH ORDINALITY
19323                true
19324            } else if f.offset_alias.is_some() {
19325                // BigQuery expansion: alias already handled above
19326                false
19327            } else {
19328                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19329                true
19330            };
19331            if should_add_alias {
19332                self.write_space();
19333                self.write_keyword("AS");
19334                self.write_space();
19335                self.generate_identifier(alias)?;
19336            }
19337        }
19338        Ok(())
19339    }
19340
19341    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19342        self.write_keyword("FILTER");
19343        self.write("(");
19344        self.generate_expression(&f.this)?;
19345        self.write(", ");
19346        self.generate_expression(&f.filter)?;
19347        self.write(")");
19348        Ok(())
19349    }
19350
19351    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19352        self.write_keyword("TRANSFORM");
19353        self.write("(");
19354        self.generate_expression(&f.this)?;
19355        self.write(", ");
19356        self.generate_expression(&f.transform)?;
19357        self.write(")");
19358        Ok(())
19359    }
19360
19361    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19362        self.write_keyword(name);
19363        self.write("(");
19364        self.generate_expression(&f.start)?;
19365        self.write(", ");
19366        self.generate_expression(&f.stop)?;
19367        if let Some(ref step) = f.step {
19368            self.write(", ");
19369            self.generate_expression(step)?;
19370        }
19371        self.write(")");
19372        Ok(())
19373    }
19374
19375    // Struct function generators
19376
19377    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19378        self.write_keyword("STRUCT");
19379        self.write("(");
19380        for (i, (name, expr)) in f.fields.iter().enumerate() {
19381            if i > 0 {
19382                self.write(", ");
19383            }
19384            if let Some(ref id) = name {
19385                self.generate_identifier(id)?;
19386                self.write(" ");
19387                self.write_keyword("AS");
19388                self.write(" ");
19389            }
19390            self.generate_expression(expr)?;
19391        }
19392        self.write(")");
19393        Ok(())
19394    }
19395
19396    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19397    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19398        // Extract named/unnamed fields from function args
19399        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19400        let mut names: Vec<Option<String>> = Vec::new();
19401        let mut values: Vec<&Expression> = Vec::new();
19402        let mut all_named = true;
19403
19404        for arg in &func.args {
19405            match arg {
19406                Expression::Alias(a) => {
19407                    names.push(Some(a.alias.name.clone()));
19408                    values.push(&a.this);
19409                }
19410                _ => {
19411                    names.push(None);
19412                    values.push(arg);
19413                    all_named = false;
19414                }
19415            }
19416        }
19417
19418        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19419            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19420            self.write("{");
19421            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19422                if i > 0 {
19423                    self.write(", ");
19424                }
19425                if let Some(n) = name {
19426                    self.write("'");
19427                    self.write(n);
19428                    self.write("'");
19429                } else {
19430                    self.write("'_");
19431                    self.write(&i.to_string());
19432                    self.write("'");
19433                }
19434                self.write(": ");
19435                self.generate_expression(value)?;
19436            }
19437            self.write("}");
19438            return Ok(());
19439        }
19440
19441        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19442            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19443            self.write_keyword("OBJECT_CONSTRUCT");
19444            self.write("(");
19445            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19446                if i > 0 {
19447                    self.write(", ");
19448                }
19449                if let Some(n) = name {
19450                    self.write("'");
19451                    self.write(n);
19452                    self.write("'");
19453                } else {
19454                    self.write("'_");
19455                    self.write(&i.to_string());
19456                    self.write("'");
19457                }
19458                self.write(", ");
19459                self.generate_expression(value)?;
19460            }
19461            self.write(")");
19462            return Ok(());
19463        }
19464
19465        if matches!(
19466            self.config.dialect,
19467            Some(DialectType::Presto) | Some(DialectType::Trino)
19468        ) {
19469            if all_named && !names.is_empty() {
19470                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19471                // Need to infer types from values
19472                self.write_keyword("CAST");
19473                self.write("(");
19474                self.write_keyword("ROW");
19475                self.write("(");
19476                for (i, value) in values.iter().enumerate() {
19477                    if i > 0 {
19478                        self.write(", ");
19479                    }
19480                    self.generate_expression(value)?;
19481                }
19482                self.write(")");
19483                self.write(" ");
19484                self.write_keyword("AS");
19485                self.write(" ");
19486                self.write_keyword("ROW");
19487                self.write("(");
19488                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19489                    if i > 0 {
19490                        self.write(", ");
19491                    }
19492                    if let Some(n) = name {
19493                        self.write(n);
19494                    }
19495                    self.write(" ");
19496                    let type_str = Self::infer_sql_type_for_presto(value);
19497                    self.write_keyword(&type_str);
19498                }
19499                self.write(")");
19500                self.write(")");
19501            } else {
19502                // Unnamed: ROW(values...)
19503                self.write_keyword("ROW");
19504                self.write("(");
19505                for (i, value) in values.iter().enumerate() {
19506                    if i > 0 {
19507                        self.write(", ");
19508                    }
19509                    self.generate_expression(value)?;
19510                }
19511                self.write(")");
19512            }
19513            return Ok(());
19514        }
19515
19516        // Default: ROW(values...) for other dialects
19517        self.write_keyword("ROW");
19518        self.write("(");
19519        for (i, value) in values.iter().enumerate() {
19520            if i > 0 {
19521                self.write(", ");
19522            }
19523            self.generate_expression(value)?;
19524        }
19525        self.write(")");
19526        Ok(())
19527    }
19528
19529    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19530    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19531        match expr {
19532            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19533            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19534                if n.contains('.') {
19535                    "DOUBLE".to_string()
19536                } else {
19537                    "INTEGER".to_string()
19538                }
19539            }
19540            Expression::Boolean(_) => "BOOLEAN".to_string(),
19541            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19542            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19543                "TIMESTAMP".to_string()
19544            }
19545            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19546                "TIMESTAMP".to_string()
19547            }
19548            Expression::Array(_) | Expression::ArrayFunc(_) => {
19549                // Try to infer element type from first element
19550                "ARRAY(VARCHAR)".to_string()
19551            }
19552            // For nested structs - generate a nested ROW type by inspecting fields
19553            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19554            Expression::Function(f) => {
19555                let up = f.name.to_uppercase();
19556                if up == "STRUCT" {
19557                    "ROW".to_string()
19558                } else if up == "CURRENT_DATE" {
19559                    "DATE".to_string()
19560                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19561                    "TIMESTAMP".to_string()
19562                } else {
19563                    "VARCHAR".to_string()
19564                }
19565            }
19566            _ => "VARCHAR".to_string(),
19567        }
19568    }
19569
19570    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19571        // DuckDB uses STRUCT_EXTRACT function syntax
19572        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19573            self.write_keyword("STRUCT_EXTRACT");
19574            self.write("(");
19575            self.generate_expression(&f.this)?;
19576            self.write(", ");
19577            // Output field name as string literal
19578            self.write("'");
19579            self.write(&f.field.name);
19580            self.write("'");
19581            self.write(")");
19582            return Ok(());
19583        }
19584        self.generate_expression(&f.this)?;
19585        self.write(".");
19586        self.generate_identifier(&f.field)
19587    }
19588
19589    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19590        self.write_keyword("NAMED_STRUCT");
19591        self.write("(");
19592        for (i, (name, value)) in f.pairs.iter().enumerate() {
19593            if i > 0 {
19594                self.write(", ");
19595            }
19596            self.generate_expression(name)?;
19597            self.write(", ");
19598            self.generate_expression(value)?;
19599        }
19600        self.write(")");
19601        Ok(())
19602    }
19603
19604    // Map function generators
19605
19606    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19607        if f.curly_brace_syntax {
19608            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19609            if f.with_map_keyword {
19610                self.write_keyword("MAP");
19611                self.write(" ");
19612            }
19613            self.write("{");
19614            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19615                if i > 0 {
19616                    self.write(", ");
19617                }
19618                self.generate_expression(key)?;
19619                self.write(": ");
19620                self.generate_expression(val)?;
19621            }
19622            self.write("}");
19623        } else {
19624            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19625            self.write_keyword("MAP");
19626            self.write("(");
19627            self.write_keyword("ARRAY");
19628            self.write("[");
19629            for (i, key) in f.keys.iter().enumerate() {
19630                if i > 0 {
19631                    self.write(", ");
19632                }
19633                self.generate_expression(key)?;
19634            }
19635            self.write("], ");
19636            self.write_keyword("ARRAY");
19637            self.write("[");
19638            for (i, val) in f.values.iter().enumerate() {
19639                if i > 0 {
19640                    self.write(", ");
19641                }
19642                self.generate_expression(val)?;
19643            }
19644            self.write("])");
19645        }
19646        Ok(())
19647    }
19648
19649    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19650        self.write_keyword(name);
19651        self.write("(");
19652        self.generate_expression(&f.this)?;
19653        self.write(", ");
19654        self.generate_expression(&f.transform)?;
19655        self.write(")");
19656        Ok(())
19657    }
19658
19659    // JSON function generators
19660
19661    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19662        use crate::dialects::DialectType;
19663
19664        // Check if we should use arrow syntax (-> or ->>)
19665        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19666
19667        if use_arrow {
19668            // Output arrow syntax: expr -> path or expr ->> path
19669            self.generate_expression(&f.this)?;
19670            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19671                self.write(" ->> ");
19672            } else {
19673                self.write(" -> ");
19674            }
19675            self.generate_expression(&f.path)?;
19676            return Ok(());
19677        }
19678
19679        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19680        if f.hash_arrow_syntax
19681            && matches!(
19682                self.config.dialect,
19683                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19684            )
19685        {
19686            self.generate_expression(&f.this)?;
19687            self.write(" #>> ");
19688            self.generate_expression(&f.path)?;
19689            return Ok(());
19690        }
19691
19692        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19693        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19694        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19695            match name {
19696                "JSON_EXTRACT_SCALAR"
19697                | "JSON_EXTRACT_PATH_TEXT"
19698                | "JSON_EXTRACT"
19699                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19700                _ => name,
19701            }
19702        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19703            match name {
19704                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19705                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19706                _ => name,
19707            }
19708        } else {
19709            name
19710        };
19711
19712        self.write_keyword(func_name);
19713        self.write("(");
19714        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19715        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19716            if let Expression::Cast(ref cast) = f.this {
19717                if matches!(cast.to, crate::expressions::DataType::Json) {
19718                    self.generate_expression(&cast.this)?;
19719                } else {
19720                    self.generate_expression(&f.this)?;
19721                }
19722            } else {
19723                self.generate_expression(&f.this)?;
19724            }
19725        } else {
19726            self.generate_expression(&f.this)?;
19727        }
19728        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19729        // decompose JSON path into separate string arguments
19730        if matches!(
19731            self.config.dialect,
19732            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19733        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19734        {
19735            if let Expression::Literal(Literal::String(ref s)) = f.path {
19736                let parts = Self::decompose_json_path(s);
19737                for part in &parts {
19738                    self.write(", '");
19739                    self.write(part);
19740                    self.write("'");
19741                }
19742            } else {
19743                self.write(", ");
19744                self.generate_expression(&f.path)?;
19745            }
19746        } else {
19747            self.write(", ");
19748            self.generate_expression(&f.path)?;
19749        }
19750
19751        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19752        // These go BEFORE the closing parenthesis
19753        if let Some(ref wrapper) = f.wrapper_option {
19754            self.write_space();
19755            self.write_keyword(wrapper);
19756        }
19757        if let Some(ref quotes) = f.quotes_option {
19758            self.write_space();
19759            self.write_keyword(quotes);
19760            if f.on_scalar_string {
19761                self.write_space();
19762                self.write_keyword("ON SCALAR STRING");
19763            }
19764        }
19765        if let Some(ref on_err) = f.on_error {
19766            self.write_space();
19767            self.write_keyword(on_err);
19768        }
19769        if let Some(ref ret_type) = f.returning {
19770            self.write_space();
19771            self.write_keyword("RETURNING");
19772            self.write_space();
19773            self.generate_data_type(ret_type)?;
19774        }
19775
19776        self.write(")");
19777        Ok(())
19778    }
19779
19780    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19781    fn dialect_supports_json_arrow(&self) -> bool {
19782        use crate::dialects::DialectType;
19783        match self.config.dialect {
19784            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19785            Some(DialectType::PostgreSQL) => true,
19786            Some(DialectType::MySQL) => true,
19787            Some(DialectType::DuckDB) => true,
19788            Some(DialectType::CockroachDB) => true,
19789            Some(DialectType::StarRocks) => true,
19790            Some(DialectType::SQLite) => true,
19791            // Other dialects use function syntax
19792            _ => false,
19793        }
19794    }
19795
19796    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19797        use crate::dialects::DialectType;
19798
19799        // PostgreSQL uses #> operator for JSONB path extraction
19800        if matches!(
19801            self.config.dialect,
19802            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19803        ) && name == "JSON_EXTRACT_PATH"
19804        {
19805            self.generate_expression(&f.this)?;
19806            self.write(" #> ");
19807            if f.paths.len() == 1 {
19808                self.generate_expression(&f.paths[0])?;
19809            } else {
19810                // Multiple paths: ARRAY[path1, path2, ...]
19811                self.write_keyword("ARRAY");
19812                self.write("[");
19813                for (i, path) in f.paths.iter().enumerate() {
19814                    if i > 0 {
19815                        self.write(", ");
19816                    }
19817                    self.generate_expression(path)?;
19818                }
19819                self.write("]");
19820            }
19821            return Ok(());
19822        }
19823
19824        self.write_keyword(name);
19825        self.write("(");
19826        self.generate_expression(&f.this)?;
19827        for path in &f.paths {
19828            self.write(", ");
19829            self.generate_expression(path)?;
19830        }
19831        self.write(")");
19832        Ok(())
19833    }
19834
19835    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19836        use crate::dialects::DialectType;
19837
19838        self.write_keyword("JSON_OBJECT");
19839        self.write("(");
19840        if f.star {
19841            self.write("*");
19842        } else {
19843            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19844            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19845            // Also respect the json_key_value_pair_sep config
19846            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19847                || matches!(
19848                    self.config.dialect,
19849                    Some(DialectType::BigQuery)
19850                        | Some(DialectType::MySQL)
19851                        | Some(DialectType::SQLite)
19852                );
19853
19854            for (i, (key, value)) in f.pairs.iter().enumerate() {
19855                if i > 0 {
19856                    self.write(", ");
19857                }
19858                self.generate_expression(key)?;
19859                if use_comma_syntax {
19860                    self.write(", ");
19861                } else {
19862                    self.write(": ");
19863                }
19864                self.generate_expression(value)?;
19865            }
19866        }
19867        if let Some(null_handling) = f.null_handling {
19868            self.write_space();
19869            match null_handling {
19870                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19871                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19872            }
19873        }
19874        if f.with_unique_keys {
19875            self.write_space();
19876            self.write_keyword("WITH UNIQUE KEYS");
19877        }
19878        if let Some(ref ret_type) = f.returning_type {
19879            self.write_space();
19880            self.write_keyword("RETURNING");
19881            self.write_space();
19882            self.generate_data_type(ret_type)?;
19883            if f.format_json {
19884                self.write_space();
19885                self.write_keyword("FORMAT JSON");
19886            }
19887            if let Some(ref enc) = f.encoding {
19888                self.write_space();
19889                self.write_keyword("ENCODING");
19890                self.write_space();
19891                self.write(enc);
19892            }
19893        }
19894        self.write(")");
19895        Ok(())
19896    }
19897
19898    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19899        self.write_keyword(name);
19900        self.write("(");
19901        self.generate_expression(&f.this)?;
19902        for (path, value) in &f.path_values {
19903            self.write(", ");
19904            self.generate_expression(path)?;
19905            self.write(", ");
19906            self.generate_expression(value)?;
19907        }
19908        self.write(")");
19909        Ok(())
19910    }
19911
19912    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19913        self.write_keyword("JSON_ARRAYAGG");
19914        self.write("(");
19915        self.generate_expression(&f.this)?;
19916        if let Some(ref order_by) = f.order_by {
19917            self.write_space();
19918            self.write_keyword("ORDER BY");
19919            self.write_space();
19920            for (i, ord) in order_by.iter().enumerate() {
19921                if i > 0 {
19922                    self.write(", ");
19923                }
19924                self.generate_ordered(ord)?;
19925            }
19926        }
19927        if let Some(null_handling) = f.null_handling {
19928            self.write_space();
19929            match null_handling {
19930                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19931                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19932            }
19933        }
19934        self.write(")");
19935        if let Some(ref filter) = f.filter {
19936            self.write_space();
19937            self.write_keyword("FILTER");
19938            self.write("(");
19939            self.write_keyword("WHERE");
19940            self.write_space();
19941            self.generate_expression(filter)?;
19942            self.write(")");
19943        }
19944        Ok(())
19945    }
19946
19947    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19948        self.write_keyword("JSON_OBJECTAGG");
19949        self.write("(");
19950        self.generate_expression(&f.key)?;
19951        self.write(": ");
19952        self.generate_expression(&f.value)?;
19953        if let Some(null_handling) = f.null_handling {
19954            self.write_space();
19955            match null_handling {
19956                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19957                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19958            }
19959        }
19960        self.write(")");
19961        if let Some(ref filter) = f.filter {
19962            self.write_space();
19963            self.write_keyword("FILTER");
19964            self.write("(");
19965            self.write_keyword("WHERE");
19966            self.write_space();
19967            self.generate_expression(filter)?;
19968            self.write(")");
19969        }
19970        Ok(())
19971    }
19972
19973    // Type casting/conversion generators
19974
19975    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19976        use crate::dialects::DialectType;
19977
19978        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19979        if self.config.dialect == Some(DialectType::Redshift) {
19980            self.write_keyword("CAST");
19981            self.write("(");
19982            self.generate_expression(&f.this)?;
19983            self.write_space();
19984            self.write_keyword("AS");
19985            self.write_space();
19986            self.generate_data_type(&f.to)?;
19987            self.write(")");
19988            return Ok(());
19989        }
19990
19991        self.write_keyword("CONVERT");
19992        self.write("(");
19993        self.generate_data_type(&f.to)?;
19994        self.write(", ");
19995        self.generate_expression(&f.this)?;
19996        if let Some(ref style) = f.style {
19997            self.write(", ");
19998            self.generate_expression(style)?;
19999        }
20000        self.write(")");
20001        Ok(())
20002    }
20003
20004    // Additional expression generators
20005
20006    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
20007        if f.colon {
20008            // DuckDB syntax: LAMBDA x : expr
20009            self.write_keyword("LAMBDA");
20010            self.write_space();
20011            for (i, param) in f.parameters.iter().enumerate() {
20012                if i > 0 {
20013                    self.write(", ");
20014                }
20015                self.generate_identifier(param)?;
20016            }
20017            self.write(" : ");
20018        } else {
20019            // Standard syntax: x -> expr or (x, y) -> expr
20020            if f.parameters.len() == 1 {
20021                self.generate_identifier(&f.parameters[0])?;
20022            } else {
20023                self.write("(");
20024                for (i, param) in f.parameters.iter().enumerate() {
20025                    if i > 0 {
20026                        self.write(", ");
20027                    }
20028                    self.generate_identifier(param)?;
20029                }
20030                self.write(")");
20031            }
20032            self.write(" -> ");
20033        }
20034        self.generate_expression(&f.body)
20035    }
20036
20037    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
20038        self.generate_identifier(&f.name)?;
20039        match f.separator {
20040            NamedArgSeparator::DArrow => self.write(" => "),
20041            NamedArgSeparator::ColonEq => self.write(" := "),
20042            NamedArgSeparator::Eq => self.write(" = "),
20043        }
20044        self.generate_expression(&f.value)
20045    }
20046
20047    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
20048        self.write_keyword(&f.prefix);
20049        self.write(" ");
20050        self.generate_expression(&f.this)
20051    }
20052
20053    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
20054        match f.style {
20055            ParameterStyle::Question => self.write("?"),
20056            ParameterStyle::Dollar => {
20057                self.write("$");
20058                if let Some(idx) = f.index {
20059                    self.write(&idx.to_string());
20060                } else if let Some(ref name) = f.name {
20061                    // Session variable like $x or $query_id
20062                    self.write(name);
20063                }
20064            }
20065            ParameterStyle::DollarBrace => {
20066                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20067                self.write("${");
20068                if let Some(ref name) = f.name {
20069                    self.write(name);
20070                }
20071                if let Some(ref expr) = f.expression {
20072                    self.write(":");
20073                    self.write(expr);
20074                }
20075                self.write("}");
20076            }
20077            ParameterStyle::Colon => {
20078                self.write(":");
20079                if let Some(idx) = f.index {
20080                    self.write(&idx.to_string());
20081                } else if let Some(ref name) = f.name {
20082                    self.write(name);
20083                }
20084            }
20085            ParameterStyle::At => {
20086                self.write("@");
20087                if let Some(ref name) = f.name {
20088                    if f.string_quoted {
20089                        self.write("'");
20090                        self.write(name);
20091                        self.write("'");
20092                    } else if f.quoted {
20093                        self.write("\"");
20094                        self.write(name);
20095                        self.write("\"");
20096                    } else {
20097                        self.write(name);
20098                    }
20099                }
20100            }
20101            ParameterStyle::DoubleAt => {
20102                self.write("@@");
20103                if let Some(ref name) = f.name {
20104                    self.write(name);
20105                }
20106            }
20107            ParameterStyle::DoubleDollar => {
20108                self.write("$$");
20109                if let Some(ref name) = f.name {
20110                    self.write(name);
20111                }
20112            }
20113            ParameterStyle::Percent => {
20114                if let Some(ref name) = f.name {
20115                    // %(name)s format
20116                    self.write("%(");
20117                    self.write(name);
20118                    self.write(")s");
20119                } else {
20120                    // %s format
20121                    self.write("%s");
20122                }
20123            }
20124            ParameterStyle::Brace => {
20125                // Spark/Databricks widget template variable: {name}
20126                // ClickHouse query parameter may include kind: {name: Type}
20127                self.write("{");
20128                if let Some(ref name) = f.name {
20129                    self.write(name);
20130                }
20131                if let Some(ref expr) = f.expression {
20132                    self.write(": ");
20133                    self.write(expr);
20134                }
20135                self.write("}");
20136            }
20137        }
20138        Ok(())
20139    }
20140
20141    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20142        self.write("?");
20143        if let Some(idx) = f.index {
20144            self.write(&idx.to_string());
20145        }
20146        Ok(())
20147    }
20148
20149    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20150        if f.is_block {
20151            self.write("/*");
20152            self.write(&f.text);
20153            self.write("*/");
20154        } else {
20155            self.write("--");
20156            self.write(&f.text);
20157        }
20158        Ok(())
20159    }
20160
20161    // Additional predicate generators
20162
20163    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20164        self.generate_expression(&f.this)?;
20165        if f.not {
20166            self.write_space();
20167            self.write_keyword("NOT");
20168        }
20169        self.write_space();
20170        self.write_keyword("SIMILAR TO");
20171        self.write_space();
20172        self.generate_expression(&f.pattern)?;
20173        if let Some(ref escape) = f.escape {
20174            self.write_space();
20175            self.write_keyword("ESCAPE");
20176            self.write_space();
20177            self.generate_expression(escape)?;
20178        }
20179        Ok(())
20180    }
20181
20182    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20183        self.generate_expression(&f.this)?;
20184        self.write_space();
20185        // Output comparison operator if present
20186        if let Some(op) = &f.op {
20187            match op {
20188                QuantifiedOp::Eq => self.write("="),
20189                QuantifiedOp::Neq => self.write("<>"),
20190                QuantifiedOp::Lt => self.write("<"),
20191                QuantifiedOp::Lte => self.write("<="),
20192                QuantifiedOp::Gt => self.write(">"),
20193                QuantifiedOp::Gte => self.write(">="),
20194            }
20195            self.write_space();
20196        }
20197        self.write_keyword(name);
20198
20199        // If the child is a Subquery, it provides its own parens — output with space
20200        if matches!(&f.subquery, Expression::Subquery(_)) {
20201            self.write_space();
20202            self.generate_expression(&f.subquery)?;
20203        } else {
20204            self.write("(");
20205
20206            let is_statement = matches!(
20207                &f.subquery,
20208                Expression::Select(_)
20209                    | Expression::Union(_)
20210                    | Expression::Intersect(_)
20211                    | Expression::Except(_)
20212            );
20213
20214            if self.config.pretty && is_statement {
20215                self.write_newline();
20216                self.indent_level += 1;
20217                self.write_indent();
20218            }
20219            self.generate_expression(&f.subquery)?;
20220            if self.config.pretty && is_statement {
20221                self.write_newline();
20222                self.indent_level -= 1;
20223                self.write_indent();
20224            }
20225            self.write(")");
20226        }
20227        Ok(())
20228    }
20229
20230    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20231        // Check if this is a simple binary form (this OVERLAPS expression)
20232        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20233            self.generate_expression(this)?;
20234            self.write_space();
20235            self.write_keyword("OVERLAPS");
20236            self.write_space();
20237            self.generate_expression(expr)?;
20238        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20239            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20240        {
20241            // Full ANSI form: (a, b) OVERLAPS (c, d)
20242            self.write("(");
20243            self.generate_expression(ls)?;
20244            self.write(", ");
20245            self.generate_expression(le)?;
20246            self.write(")");
20247            self.write_space();
20248            self.write_keyword("OVERLAPS");
20249            self.write_space();
20250            self.write("(");
20251            self.generate_expression(rs)?;
20252            self.write(", ");
20253            self.generate_expression(re)?;
20254            self.write(")");
20255        }
20256        Ok(())
20257    }
20258
20259    // Type conversion generators
20260
20261    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20262        use crate::dialects::DialectType;
20263
20264        // SingleStore uses !:> syntax for try cast
20265        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20266            self.generate_expression(&cast.this)?;
20267            self.write(" !:> ");
20268            self.generate_data_type(&cast.to)?;
20269            return Ok(());
20270        }
20271
20272        // Teradata uses TRYCAST (no underscore)
20273        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20274            self.write_keyword("TRYCAST");
20275            self.write("(");
20276            self.generate_expression(&cast.this)?;
20277            self.write_space();
20278            self.write_keyword("AS");
20279            self.write_space();
20280            self.generate_data_type(&cast.to)?;
20281            self.write(")");
20282            return Ok(());
20283        }
20284
20285        // Dialects without TRY_CAST: generate as regular CAST
20286        let keyword = if matches!(
20287            self.config.dialect,
20288            Some(DialectType::Hive)
20289                | Some(DialectType::MySQL)
20290                | Some(DialectType::SQLite)
20291                | Some(DialectType::Oracle)
20292                | Some(DialectType::ClickHouse)
20293                | Some(DialectType::Redshift)
20294                | Some(DialectType::PostgreSQL)
20295                | Some(DialectType::StarRocks)
20296                | Some(DialectType::Doris)
20297        ) {
20298            "CAST"
20299        } else {
20300            "TRY_CAST"
20301        };
20302
20303        self.write_keyword(keyword);
20304        self.write("(");
20305        self.generate_expression(&cast.this)?;
20306        self.write_space();
20307        self.write_keyword("AS");
20308        self.write_space();
20309        self.generate_data_type(&cast.to)?;
20310
20311        // Output FORMAT clause if present
20312        if let Some(format) = &cast.format {
20313            self.write_space();
20314            self.write_keyword("FORMAT");
20315            self.write_space();
20316            self.generate_expression(format)?;
20317        }
20318
20319        self.write(")");
20320        Ok(())
20321    }
20322
20323    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20324        self.write_keyword("SAFE_CAST");
20325        self.write("(");
20326        self.generate_expression(&cast.this)?;
20327        self.write_space();
20328        self.write_keyword("AS");
20329        self.write_space();
20330        self.generate_data_type(&cast.to)?;
20331
20332        // Output FORMAT clause if present
20333        if let Some(format) = &cast.format {
20334            self.write_space();
20335            self.write_keyword("FORMAT");
20336            self.write_space();
20337            self.generate_expression(format)?;
20338        }
20339
20340        self.write(")");
20341        Ok(())
20342    }
20343
20344    // Array/struct/map access generators
20345
20346    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20347        self.generate_expression(&s.this)?;
20348        self.write("[");
20349        self.generate_expression(&s.index)?;
20350        self.write("]");
20351        Ok(())
20352    }
20353
20354    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20355        self.generate_expression(&d.this)?;
20356        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20357        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20358        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20359            && matches!(
20360                &d.this,
20361                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20362            );
20363        if use_colon {
20364            self.write(":");
20365        } else {
20366            self.write(".");
20367        }
20368        self.generate_identifier(&d.field)
20369    }
20370
20371    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20372        self.generate_expression(&m.this)?;
20373        self.write(".");
20374        // Method names after a dot should not be quoted based on reserved keywords
20375        // Only quote if explicitly marked as quoted in the AST
20376        if m.method.quoted {
20377            let q = self.config.identifier_quote;
20378            self.write(&format!("{}{}{}", q, m.method.name, q));
20379        } else {
20380            self.write(&m.method.name);
20381        }
20382        self.write("(");
20383        for (i, arg) in m.args.iter().enumerate() {
20384            if i > 0 {
20385                self.write(", ");
20386            }
20387            self.generate_expression(arg)?;
20388        }
20389        self.write(")");
20390        Ok(())
20391    }
20392
20393    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20394        // Check if we need to wrap the inner expression in parentheses
20395        // JSON arrow expressions have lower precedence than array subscript
20396        let needs_parens = matches!(
20397            &s.this,
20398            Expression::JsonExtract(f) if f.arrow_syntax
20399        ) || matches!(
20400            &s.this,
20401            Expression::JsonExtractScalar(f) if f.arrow_syntax
20402        );
20403
20404        if needs_parens {
20405            self.write("(");
20406        }
20407        self.generate_expression(&s.this)?;
20408        if needs_parens {
20409            self.write(")");
20410        }
20411        self.write("[");
20412        if let Some(start) = &s.start {
20413            self.generate_expression(start)?;
20414        }
20415        self.write(":");
20416        if let Some(end) = &s.end {
20417            self.generate_expression(end)?;
20418        }
20419        self.write("]");
20420        Ok(())
20421    }
20422
20423    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20424        // Generate left expression, but skip trailing comments if they're already in left_comments
20425        // to avoid duplication (comments are captured as both expr.trailing_comments
20426        // and BinaryOp.left_comments during parsing)
20427        match &op.left {
20428            Expression::Column(col) => {
20429                // Generate column with trailing comments but skip them if they're
20430                // already captured in BinaryOp.left_comments to avoid duplication
20431                if let Some(table) = &col.table {
20432                    self.generate_identifier(table)?;
20433                    self.write(".");
20434                }
20435                self.generate_identifier(&col.name)?;
20436                // Oracle-style join marker (+)
20437                if col.join_mark && self.config.supports_column_join_marks {
20438                    self.write(" (+)");
20439                }
20440                // Output column trailing comments if they're not already in left_comments
20441                if op.left_comments.is_empty() {
20442                    for comment in &col.trailing_comments {
20443                        self.write_space();
20444                        self.write_formatted_comment(comment);
20445                    }
20446                }
20447            }
20448            Expression::Add(inner_op)
20449            | Expression::Sub(inner_op)
20450            | Expression::Mul(inner_op)
20451            | Expression::Div(inner_op)
20452            | Expression::Concat(inner_op) => {
20453                // Generate binary op without its trailing comments
20454                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20455                    Expression::Add(_) => "+",
20456                    Expression::Sub(_) => "-",
20457                    Expression::Mul(_) => "*",
20458                    Expression::Div(_) => "/",
20459                    Expression::Concat(_) => "||",
20460                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20461                })?;
20462            }
20463            _ => {
20464                self.generate_expression(&op.left)?;
20465            }
20466        }
20467        // Output comments after left operand
20468        for comment in &op.left_comments {
20469            self.write_space();
20470            self.write_formatted_comment(comment);
20471        }
20472        if self.config.pretty
20473            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20474            && (operator == "AND" || operator == "OR")
20475        {
20476            self.write_newline();
20477            self.write_indent();
20478            self.write_keyword(operator);
20479        } else {
20480            self.write_space();
20481            if operator.chars().all(|c| c.is_alphabetic()) {
20482                self.write_keyword(operator);
20483            } else {
20484                self.write(operator);
20485            }
20486        }
20487        // Output comments after operator (before right operand)
20488        for comment in &op.operator_comments {
20489            self.write_space();
20490            self.write_formatted_comment(comment);
20491        }
20492        self.write_space();
20493        self.generate_expression(&op.right)?;
20494        // Output trailing comments after right operand
20495        for comment in &op.trailing_comments {
20496            self.write_space();
20497            self.write_formatted_comment(comment);
20498        }
20499        Ok(())
20500    }
20501
20502    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
20503        let keyword = connector.keyword();
20504        let Some(terms) = self.flatten_connector_terms(op, connector) else {
20505            return self.generate_binary_op(op, keyword);
20506        };
20507
20508        self.generate_expression(terms[0])?;
20509        for term in terms.iter().skip(1) {
20510            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20511                self.write_newline();
20512                self.write_indent();
20513                self.write_keyword(keyword);
20514            } else {
20515                self.write_space();
20516                self.write_keyword(keyword);
20517            }
20518            self.write_space();
20519            self.generate_expression(term)?;
20520        }
20521
20522        Ok(())
20523    }
20524
20525    fn flatten_connector_terms<'a>(
20526        &self,
20527        root: &'a BinaryOp,
20528        connector: ConnectorOperator,
20529    ) -> Option<Vec<&'a Expression>> {
20530        if !root.left_comments.is_empty()
20531            || !root.operator_comments.is_empty()
20532            || !root.trailing_comments.is_empty()
20533        {
20534            return None;
20535        }
20536
20537        let mut terms = Vec::new();
20538        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
20539
20540        while let Some(expr) = stack.pop() {
20541            match (connector, expr) {
20542                (ConnectorOperator::And, Expression::And(inner))
20543                    if inner.left_comments.is_empty()
20544                        && inner.operator_comments.is_empty()
20545                        && inner.trailing_comments.is_empty() =>
20546                {
20547                    stack.push(&inner.right);
20548                    stack.push(&inner.left);
20549                }
20550                (ConnectorOperator::Or, Expression::Or(inner))
20551                    if inner.left_comments.is_empty()
20552                        && inner.operator_comments.is_empty()
20553                        && inner.trailing_comments.is_empty() =>
20554                {
20555                    stack.push(&inner.right);
20556                    stack.push(&inner.left);
20557                }
20558                _ => terms.push(expr),
20559            }
20560        }
20561
20562        if terms.len() > 1 {
20563            Some(terms)
20564        } else {
20565            None
20566        }
20567    }
20568
20569    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20570    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20571        self.generate_expression(&op.left)?;
20572        self.write_space();
20573        // Drill backtick-quotes ILIKE
20574        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20575            self.write("`ILIKE`");
20576        } else {
20577            self.write_keyword(operator);
20578        }
20579        if let Some(quantifier) = &op.quantifier {
20580            self.write_space();
20581            self.write_keyword(quantifier);
20582        }
20583        self.write_space();
20584        self.generate_expression(&op.right)?;
20585        if let Some(escape) = &op.escape {
20586            self.write_space();
20587            self.write_keyword("ESCAPE");
20588            self.write_space();
20589            self.generate_expression(escape)?;
20590        }
20591        Ok(())
20592    }
20593
20594    /// Generate null-safe equality
20595    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20596    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20597        use crate::dialects::DialectType;
20598        self.generate_expression(&op.left)?;
20599        self.write_space();
20600        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20601            self.write("<=>");
20602        } else {
20603            self.write_keyword("IS NOT DISTINCT FROM");
20604        }
20605        self.write_space();
20606        self.generate_expression(&op.right)?;
20607        Ok(())
20608    }
20609
20610    /// Generate IS DISTINCT FROM (null-safe inequality)
20611    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20612        self.generate_expression(&op.left)?;
20613        self.write_space();
20614        self.write_keyword("IS DISTINCT FROM");
20615        self.write_space();
20616        self.generate_expression(&op.right)?;
20617        Ok(())
20618    }
20619
20620    /// Generate binary op without trailing comments (used when nested inside another binary op)
20621    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20622        // Generate left expression, but skip trailing comments
20623        match &op.left {
20624            Expression::Column(col) => {
20625                if let Some(table) = &col.table {
20626                    self.generate_identifier(table)?;
20627                    self.write(".");
20628                }
20629                self.generate_identifier(&col.name)?;
20630                // Oracle-style join marker (+)
20631                if col.join_mark && self.config.supports_column_join_marks {
20632                    self.write(" (+)");
20633                }
20634            }
20635            Expression::Add(inner_op)
20636            | Expression::Sub(inner_op)
20637            | Expression::Mul(inner_op)
20638            | Expression::Div(inner_op)
20639            | Expression::Concat(inner_op) => {
20640                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20641                    Expression::Add(_) => "+",
20642                    Expression::Sub(_) => "-",
20643                    Expression::Mul(_) => "*",
20644                    Expression::Div(_) => "/",
20645                    Expression::Concat(_) => "||",
20646                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20647                })?;
20648            }
20649            _ => {
20650                self.generate_expression(&op.left)?;
20651            }
20652        }
20653        // Output left_comments
20654        for comment in &op.left_comments {
20655            self.write_space();
20656            self.write_formatted_comment(comment);
20657        }
20658        self.write_space();
20659        if operator.chars().all(|c| c.is_alphabetic()) {
20660            self.write_keyword(operator);
20661        } else {
20662            self.write(operator);
20663        }
20664        // Output operator_comments
20665        for comment in &op.operator_comments {
20666            self.write_space();
20667            self.write_formatted_comment(comment);
20668        }
20669        self.write_space();
20670        // Generate right expression, but skip trailing comments if it's a Column
20671        // (the parent's left_comments will output them)
20672        match &op.right {
20673            Expression::Column(col) => {
20674                if let Some(table) = &col.table {
20675                    self.generate_identifier(table)?;
20676                    self.write(".");
20677                }
20678                self.generate_identifier(&col.name)?;
20679                // Oracle-style join marker (+)
20680                if col.join_mark && self.config.supports_column_join_marks {
20681                    self.write(" (+)");
20682                }
20683            }
20684            _ => {
20685                self.generate_expression(&op.right)?;
20686            }
20687        }
20688        // Skip trailing_comments - parent will handle them via its left_comments
20689        Ok(())
20690    }
20691
20692    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20693        if operator.chars().all(|c| c.is_alphabetic()) {
20694            self.write_keyword(operator);
20695            self.write_space();
20696        } else {
20697            self.write(operator);
20698            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20699            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20700                self.write_space();
20701            }
20702        }
20703        self.generate_expression(&op.this)
20704    }
20705
20706    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20707        // Generic mode supports two styles for negated IN:
20708        // - Prefix: NOT a IN (...)
20709        // - Infix:  a NOT IN (...)
20710        let is_generic =
20711            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20712        let use_prefix_not =
20713            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20714        if use_prefix_not {
20715            self.write_keyword("NOT");
20716            self.write_space();
20717        }
20718        self.generate_expression(&in_expr.this)?;
20719        if in_expr.global {
20720            self.write_space();
20721            self.write_keyword("GLOBAL");
20722        }
20723        if in_expr.not && !use_prefix_not {
20724            self.write_space();
20725            self.write_keyword("NOT");
20726        }
20727        self.write_space();
20728        self.write_keyword("IN");
20729
20730        // BigQuery: IN UNNEST(expr)
20731        if let Some(unnest_expr) = &in_expr.unnest {
20732            self.write_space();
20733            self.write_keyword("UNNEST");
20734            self.write("(");
20735            self.generate_expression(unnest_expr)?;
20736            self.write(")");
20737            return Ok(());
20738        }
20739
20740        if let Some(query) = &in_expr.query {
20741            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20742            // vs a subquery (col IN (SELECT ...))
20743            let is_bare = in_expr.expressions.is_empty()
20744                && !matches!(
20745                    query,
20746                    Expression::Select(_)
20747                        | Expression::Union(_)
20748                        | Expression::Intersect(_)
20749                        | Expression::Except(_)
20750                        | Expression::Subquery(_)
20751                );
20752            if is_bare {
20753                // Bare identifier: no parentheses
20754                self.write_space();
20755                self.generate_expression(query)?;
20756            } else {
20757                // Subquery: with parentheses
20758                self.write(" (");
20759                let is_statement = matches!(
20760                    query,
20761                    Expression::Select(_)
20762                        | Expression::Union(_)
20763                        | Expression::Intersect(_)
20764                        | Expression::Except(_)
20765                        | Expression::Subquery(_)
20766                );
20767                if self.config.pretty && is_statement {
20768                    self.write_newline();
20769                    self.indent_level += 1;
20770                    self.write_indent();
20771                }
20772                self.generate_expression(query)?;
20773                if self.config.pretty && is_statement {
20774                    self.write_newline();
20775                    self.indent_level -= 1;
20776                    self.write_indent();
20777                }
20778                self.write(")");
20779            }
20780        } else {
20781            // DuckDB: IN without parentheses for single expression that is NOT a literal
20782            // (array/list membership like 'red' IN tbl.flags)
20783            // ClickHouse: IN without parentheses for single non-array expressions
20784            let is_duckdb = matches!(
20785                self.config.dialect,
20786                Some(crate::dialects::DialectType::DuckDB)
20787            );
20788            let is_clickhouse = matches!(
20789                self.config.dialect,
20790                Some(crate::dialects::DialectType::ClickHouse)
20791            );
20792            let single_expr = in_expr.expressions.len() == 1;
20793            if is_clickhouse && single_expr {
20794                if let Expression::Array(arr) = &in_expr.expressions[0] {
20795                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20796                    self.write(" (");
20797                    for (i, expr) in arr.expressions.iter().enumerate() {
20798                        if i > 0 {
20799                            self.write(", ");
20800                        }
20801                        self.generate_expression(expr)?;
20802                    }
20803                    self.write(")");
20804                } else {
20805                    self.write_space();
20806                    self.generate_expression(&in_expr.expressions[0])?;
20807                }
20808            } else {
20809                let is_bare_ref = single_expr
20810                    && matches!(
20811                        &in_expr.expressions[0],
20812                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20813                    );
20814                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20815                    // Bare field reference (no parens in source): IN identifier
20816                    // Also DuckDB: IN without parentheses for array/list membership
20817                    self.write_space();
20818                    self.generate_expression(&in_expr.expressions[0])?;
20819                } else {
20820                    // Standard IN (list)
20821                    self.write(" (");
20822                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20823                        if i > 0 {
20824                            self.write(", ");
20825                        }
20826                        self.generate_expression(expr)?;
20827                    }
20828                    self.write(")");
20829                }
20830            }
20831        }
20832
20833        Ok(())
20834    }
20835
20836    fn generate_between(&mut self, between: &Between) -> Result<()> {
20837        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20838        let use_prefix_not = between.not
20839            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20840        if use_prefix_not {
20841            self.write_keyword("NOT");
20842            self.write_space();
20843        }
20844        self.generate_expression(&between.this)?;
20845        if between.not && !use_prefix_not {
20846            self.write_space();
20847            self.write_keyword("NOT");
20848        }
20849        self.write_space();
20850        self.write_keyword("BETWEEN");
20851        // Emit SYMMETRIC/ASYMMETRIC if present
20852        if let Some(sym) = between.symmetric {
20853            if sym {
20854                self.write(" SYMMETRIC");
20855            } else {
20856                self.write(" ASYMMETRIC");
20857            }
20858        }
20859        self.write_space();
20860        self.generate_expression(&between.low)?;
20861        self.write_space();
20862        self.write_keyword("AND");
20863        self.write_space();
20864        self.generate_expression(&between.high)
20865    }
20866
20867    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20868        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20869        let use_prefix_not = is_null.not
20870            && (self.config.dialect.is_none()
20871                || self.config.dialect == Some(DialectType::Generic)
20872                || is_null.postfix_form);
20873        if use_prefix_not {
20874            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20875            self.write_keyword("NOT");
20876            self.write_space();
20877            self.generate_expression(&is_null.this)?;
20878            self.write_space();
20879            self.write_keyword("IS");
20880            self.write_space();
20881            self.write_keyword("NULL");
20882        } else {
20883            self.generate_expression(&is_null.this)?;
20884            self.write_space();
20885            self.write_keyword("IS");
20886            if is_null.not {
20887                self.write_space();
20888                self.write_keyword("NOT");
20889            }
20890            self.write_space();
20891            self.write_keyword("NULL");
20892        }
20893        Ok(())
20894    }
20895
20896    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20897        self.generate_expression(&is_true.this)?;
20898        self.write_space();
20899        self.write_keyword("IS");
20900        if is_true.not {
20901            self.write_space();
20902            self.write_keyword("NOT");
20903        }
20904        self.write_space();
20905        self.write_keyword("TRUE");
20906        Ok(())
20907    }
20908
20909    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20910        self.generate_expression(&is_false.this)?;
20911        self.write_space();
20912        self.write_keyword("IS");
20913        if is_false.not {
20914            self.write_space();
20915            self.write_keyword("NOT");
20916        }
20917        self.write_space();
20918        self.write_keyword("FALSE");
20919        Ok(())
20920    }
20921
20922    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20923        self.generate_expression(&is_json.this)?;
20924        self.write_space();
20925        self.write_keyword("IS");
20926        if is_json.negated {
20927            self.write_space();
20928            self.write_keyword("NOT");
20929        }
20930        self.write_space();
20931        self.write_keyword("JSON");
20932
20933        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20934        if let Some(ref json_type) = is_json.json_type {
20935            self.write_space();
20936            self.write_keyword(json_type);
20937        }
20938
20939        // Output key uniqueness constraint if specified
20940        match &is_json.unique_keys {
20941            Some(JsonUniqueKeys::With) => {
20942                self.write_space();
20943                self.write_keyword("WITH UNIQUE KEYS");
20944            }
20945            Some(JsonUniqueKeys::Without) => {
20946                self.write_space();
20947                self.write_keyword("WITHOUT UNIQUE KEYS");
20948            }
20949            Some(JsonUniqueKeys::Shorthand) => {
20950                self.write_space();
20951                self.write_keyword("UNIQUE KEYS");
20952            }
20953            None => {}
20954        }
20955
20956        Ok(())
20957    }
20958
20959    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20960        self.generate_expression(&is_expr.left)?;
20961        self.write_space();
20962        self.write_keyword("IS");
20963        self.write_space();
20964        self.generate_expression(&is_expr.right)
20965    }
20966
20967    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20968        if exists.not {
20969            self.write_keyword("NOT");
20970            self.write_space();
20971        }
20972        self.write_keyword("EXISTS");
20973        self.write("(");
20974        let is_statement = matches!(
20975            &exists.this,
20976            Expression::Select(_)
20977                | Expression::Union(_)
20978                | Expression::Intersect(_)
20979                | Expression::Except(_)
20980        );
20981        if self.config.pretty && is_statement {
20982            self.write_newline();
20983            self.indent_level += 1;
20984            self.write_indent();
20985            self.generate_expression(&exists.this)?;
20986            self.write_newline();
20987            self.indent_level -= 1;
20988            self.write_indent();
20989            self.write(")");
20990        } else {
20991            self.generate_expression(&exists.this)?;
20992            self.write(")");
20993        }
20994        Ok(())
20995    }
20996
20997    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20998        self.generate_expression(&op.left)?;
20999        self.write_space();
21000        self.write_keyword("MEMBER OF");
21001        self.write("(");
21002        self.generate_expression(&op.right)?;
21003        self.write(")");
21004        Ok(())
21005    }
21006
21007    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
21008        if subquery.lateral {
21009            self.write_keyword("LATERAL");
21010            self.write_space();
21011        }
21012
21013        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
21014        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
21015        // to carry the LIMIT modifier without adding more parens
21016        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
21017            matches!(
21018                &p.this,
21019                Expression::Select(_)
21020                    | Expression::Union(_)
21021                    | Expression::Intersect(_)
21022                    | Expression::Except(_)
21023                    | Expression::Subquery(_)
21024            )
21025        } else {
21026            false
21027        };
21028
21029        // Check if inner expression is a statement for pretty formatting
21030        let is_statement = matches!(
21031            &subquery.this,
21032            Expression::Select(_)
21033                | Expression::Union(_)
21034                | Expression::Intersect(_)
21035                | Expression::Except(_)
21036                | Expression::Merge(_)
21037        );
21038
21039        if !skip_outer_parens {
21040            self.write("(");
21041            if self.config.pretty && is_statement {
21042                self.write_newline();
21043                self.indent_level += 1;
21044                self.write_indent();
21045            }
21046        }
21047        self.generate_expression(&subquery.this)?;
21048
21049        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
21050        if subquery.modifiers_inside {
21051            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
21052            if let Some(order_by) = &subquery.order_by {
21053                self.write_space();
21054                self.write_keyword("ORDER BY");
21055                self.write_space();
21056                for (i, ord) in order_by.expressions.iter().enumerate() {
21057                    if i > 0 {
21058                        self.write(", ");
21059                    }
21060                    self.generate_ordered(ord)?;
21061                }
21062            }
21063
21064            if let Some(limit) = &subquery.limit {
21065                self.write_space();
21066                self.write_keyword("LIMIT");
21067                self.write_space();
21068                self.generate_expression(&limit.this)?;
21069                if limit.percent {
21070                    self.write_space();
21071                    self.write_keyword("PERCENT");
21072                }
21073            }
21074
21075            if let Some(offset) = &subquery.offset {
21076                self.write_space();
21077                self.write_keyword("OFFSET");
21078                self.write_space();
21079                self.generate_expression(&offset.this)?;
21080            }
21081        }
21082
21083        if !skip_outer_parens {
21084            if self.config.pretty && is_statement {
21085                self.write_newline();
21086                self.indent_level -= 1;
21087                self.write_indent();
21088            }
21089            self.write(")");
21090        }
21091
21092        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21093        if !subquery.modifiers_inside {
21094            if let Some(order_by) = &subquery.order_by {
21095                self.write_space();
21096                self.write_keyword("ORDER BY");
21097                self.write_space();
21098                for (i, ord) in order_by.expressions.iter().enumerate() {
21099                    if i > 0 {
21100                        self.write(", ");
21101                    }
21102                    self.generate_ordered(ord)?;
21103                }
21104            }
21105
21106            if let Some(limit) = &subquery.limit {
21107                self.write_space();
21108                self.write_keyword("LIMIT");
21109                self.write_space();
21110                self.generate_expression(&limit.this)?;
21111                if limit.percent {
21112                    self.write_space();
21113                    self.write_keyword("PERCENT");
21114                }
21115            }
21116
21117            if let Some(offset) = &subquery.offset {
21118                self.write_space();
21119                self.write_keyword("OFFSET");
21120                self.write_space();
21121                self.generate_expression(&offset.this)?;
21122            }
21123
21124            // Generate DISTRIBUTE BY (Hive/Spark)
21125            if let Some(distribute_by) = &subquery.distribute_by {
21126                self.write_space();
21127                self.write_keyword("DISTRIBUTE BY");
21128                self.write_space();
21129                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21130                    if i > 0 {
21131                        self.write(", ");
21132                    }
21133                    self.generate_expression(expr)?;
21134                }
21135            }
21136
21137            // Generate SORT BY (Hive/Spark)
21138            if let Some(sort_by) = &subquery.sort_by {
21139                self.write_space();
21140                self.write_keyword("SORT BY");
21141                self.write_space();
21142                for (i, ord) in sort_by.expressions.iter().enumerate() {
21143                    if i > 0 {
21144                        self.write(", ");
21145                    }
21146                    self.generate_ordered(ord)?;
21147                }
21148            }
21149
21150            // Generate CLUSTER BY (Hive/Spark)
21151            if let Some(cluster_by) = &subquery.cluster_by {
21152                self.write_space();
21153                self.write_keyword("CLUSTER BY");
21154                self.write_space();
21155                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21156                    if i > 0 {
21157                        self.write(", ");
21158                    }
21159                    self.generate_ordered(ord)?;
21160                }
21161            }
21162        }
21163
21164        if let Some(alias) = &subquery.alias {
21165            self.write_space();
21166            // Oracle doesn't use AS for subquery aliases
21167            let skip_as = matches!(
21168                self.config.dialect,
21169                Some(crate::dialects::DialectType::Oracle)
21170            );
21171            if !skip_as {
21172                self.write_keyword("AS");
21173                self.write_space();
21174            }
21175            self.generate_identifier(alias)?;
21176            if !subquery.column_aliases.is_empty() {
21177                self.write("(");
21178                for (i, col) in subquery.column_aliases.iter().enumerate() {
21179                    if i > 0 {
21180                        self.write(", ");
21181                    }
21182                    self.generate_identifier(col)?;
21183                }
21184                self.write(")");
21185            }
21186        }
21187        // Output trailing comments
21188        for comment in &subquery.trailing_comments {
21189            self.write(" ");
21190            self.write_formatted_comment(comment);
21191        }
21192        Ok(())
21193    }
21194
21195    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21196        // Generate WITH clause if present
21197        if let Some(ref with) = pivot.with {
21198            self.generate_with(with)?;
21199            self.write_space();
21200        }
21201
21202        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21203
21204        // Check for Redshift UNPIVOT in FROM clause:
21205        // UNPIVOT expr [AS val AT attr]
21206        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21207        let is_redshift_unpivot = pivot.unpivot
21208            && pivot.expressions.is_empty()
21209            && pivot.fields.is_empty()
21210            && pivot.using.is_empty()
21211            && pivot.into.is_none()
21212            && !matches!(&pivot.this, Expression::Null(_));
21213
21214        if is_redshift_unpivot {
21215            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21216            self.write_keyword("UNPIVOT");
21217            self.write_space();
21218            self.generate_expression(&pivot.this)?;
21219            // Alias - for Redshift it can be "val AT attr" format
21220            if let Some(alias) = &pivot.alias {
21221                self.write_space();
21222                self.write_keyword("AS");
21223                self.write_space();
21224                // The alias might contain " AT " for the attr part
21225                self.write(&alias.name);
21226            }
21227            return Ok(());
21228        }
21229
21230        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21231        let is_simplified = !pivot.using.is_empty()
21232            || pivot.into.is_some()
21233            || (pivot.fields.is_empty()
21234                && !pivot.expressions.is_empty()
21235                && !matches!(&pivot.this, Expression::Null(_)));
21236
21237        if is_simplified {
21238            // DuckDB simplified syntax:
21239            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21240            //   UNPIVOT table ON cols INTO NAME col VALUE col
21241            self.write_keyword(direction);
21242            self.write_space();
21243            self.generate_expression(&pivot.this)?;
21244
21245            if !pivot.expressions.is_empty() {
21246                self.write_space();
21247                self.write_keyword("ON");
21248                self.write_space();
21249                for (i, expr) in pivot.expressions.iter().enumerate() {
21250                    if i > 0 {
21251                        self.write(", ");
21252                    }
21253                    self.generate_expression(expr)?;
21254                }
21255            }
21256
21257            // INTO (for UNPIVOT)
21258            if let Some(into) = &pivot.into {
21259                self.write_space();
21260                self.write_keyword("INTO");
21261                self.write_space();
21262                self.generate_expression(into)?;
21263            }
21264
21265            // USING (for PIVOT)
21266            if !pivot.using.is_empty() {
21267                self.write_space();
21268                self.write_keyword("USING");
21269                self.write_space();
21270                for (i, expr) in pivot.using.iter().enumerate() {
21271                    if i > 0 {
21272                        self.write(", ");
21273                    }
21274                    self.generate_expression(expr)?;
21275                }
21276            }
21277
21278            // GROUP BY
21279            if let Some(group) = &pivot.group {
21280                self.write_space();
21281                self.generate_expression(group)?;
21282            }
21283        } else {
21284            // Standard syntax:
21285            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21286            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21287            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21288            if !matches!(&pivot.this, Expression::Null(_)) {
21289                self.generate_expression(&pivot.this)?;
21290                self.write_space();
21291            }
21292            self.write_keyword(direction);
21293            self.write("(");
21294
21295            // Aggregation expressions
21296            for (i, expr) in pivot.expressions.iter().enumerate() {
21297                if i > 0 {
21298                    self.write(", ");
21299                }
21300                self.generate_expression(expr)?;
21301            }
21302
21303            // FOR...IN fields
21304            if !pivot.fields.is_empty() {
21305                if !pivot.expressions.is_empty() {
21306                    self.write_space();
21307                }
21308                self.write_keyword("FOR");
21309                self.write_space();
21310                for (i, field) in pivot.fields.iter().enumerate() {
21311                    if i > 0 {
21312                        self.write_space();
21313                    }
21314                    // field is an In expression: column IN (values)
21315                    self.generate_expression(field)?;
21316                }
21317            }
21318
21319            // DEFAULT ON NULL
21320            if let Some(default_val) = &pivot.default_on_null {
21321                self.write_space();
21322                self.write_keyword("DEFAULT ON NULL");
21323                self.write(" (");
21324                self.generate_expression(default_val)?;
21325                self.write(")");
21326            }
21327
21328            // GROUP BY inside PIVOT parens
21329            if let Some(group) = &pivot.group {
21330                self.write_space();
21331                self.generate_expression(group)?;
21332            }
21333
21334            self.write(")");
21335        }
21336
21337        // Alias
21338        if let Some(alias) = &pivot.alias {
21339            self.write_space();
21340            self.write_keyword("AS");
21341            self.write_space();
21342            self.generate_identifier(alias)?;
21343        }
21344
21345        Ok(())
21346    }
21347
21348    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21349        self.generate_expression(&unpivot.this)?;
21350        self.write_space();
21351        self.write_keyword("UNPIVOT");
21352        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21353        if let Some(include) = unpivot.include_nulls {
21354            self.write_space();
21355            if include {
21356                self.write_keyword("INCLUDE NULLS");
21357            } else {
21358                self.write_keyword("EXCLUDE NULLS");
21359            }
21360            self.write_space();
21361        }
21362        self.write("(");
21363        if unpivot.value_column_parenthesized {
21364            self.write("(");
21365        }
21366        self.generate_identifier(&unpivot.value_column)?;
21367        // Output additional value columns if present
21368        for extra_col in &unpivot.extra_value_columns {
21369            self.write(", ");
21370            self.generate_identifier(extra_col)?;
21371        }
21372        if unpivot.value_column_parenthesized {
21373            self.write(")");
21374        }
21375        self.write_space();
21376        self.write_keyword("FOR");
21377        self.write_space();
21378        self.generate_identifier(&unpivot.name_column)?;
21379        self.write_space();
21380        self.write_keyword("IN");
21381        self.write(" (");
21382        for (i, col) in unpivot.columns.iter().enumerate() {
21383            if i > 0 {
21384                self.write(", ");
21385            }
21386            self.generate_expression(col)?;
21387        }
21388        self.write("))");
21389        if let Some(alias) = &unpivot.alias {
21390            self.write_space();
21391            self.write_keyword("AS");
21392            self.write_space();
21393            self.generate_identifier(alias)?;
21394        }
21395        Ok(())
21396    }
21397
21398    fn generate_values(&mut self, values: &Values) -> Result<()> {
21399        self.write_keyword("VALUES");
21400        for (i, row) in values.expressions.iter().enumerate() {
21401            if i > 0 {
21402                self.write(",");
21403            }
21404            self.write(" (");
21405            for (j, expr) in row.expressions.iter().enumerate() {
21406                if j > 0 {
21407                    self.write(", ");
21408                }
21409                self.generate_expression(expr)?;
21410            }
21411            self.write(")");
21412        }
21413        if let Some(alias) = &values.alias {
21414            self.write_space();
21415            self.write_keyword("AS");
21416            self.write_space();
21417            self.generate_identifier(alias)?;
21418            if !values.column_aliases.is_empty() {
21419                self.write("(");
21420                for (i, col) in values.column_aliases.iter().enumerate() {
21421                    if i > 0 {
21422                        self.write(", ");
21423                    }
21424                    self.generate_identifier(col)?;
21425                }
21426                self.write(")");
21427            }
21428        }
21429        Ok(())
21430    }
21431
21432    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21433        // Apply struct name inheritance for target dialects that need it
21434        let needs_inheritance = matches!(
21435            self.config.dialect,
21436            Some(DialectType::DuckDB)
21437                | Some(DialectType::Spark)
21438                | Some(DialectType::Databricks)
21439                | Some(DialectType::Hive)
21440                | Some(DialectType::Snowflake)
21441                | Some(DialectType::Presto)
21442                | Some(DialectType::Trino)
21443        );
21444        let propagated: Vec<Expression>;
21445        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21446            propagated = Self::inherit_struct_field_names(&arr.expressions);
21447            &propagated
21448        } else {
21449            &arr.expressions
21450        };
21451
21452        // Generic mode: ARRAY(1, 2, 3) with parentheses
21453        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21454        let use_parens =
21455            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21456        if !self.config.array_bracket_only {
21457            self.write_keyword("ARRAY");
21458        }
21459        if use_parens {
21460            self.write("(");
21461        } else {
21462            self.write("[");
21463        }
21464        for (i, expr) in expressions.iter().enumerate() {
21465            if i > 0 {
21466                self.write(", ");
21467            }
21468            self.generate_expression(expr)?;
21469        }
21470        if use_parens {
21471            self.write(")");
21472        } else {
21473            self.write("]");
21474        }
21475        Ok(())
21476    }
21477
21478    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21479        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21480        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21481        if tuple.expressions.len() == 2 {
21482            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21483                // First element is the function/expression, second is the TableAlias
21484                self.generate_expression(&tuple.expressions[0])?;
21485                self.write_space();
21486                self.write_keyword("AS");
21487                self.write_space();
21488                self.generate_expression(&tuple.expressions[1])?;
21489                return Ok(());
21490            }
21491        }
21492
21493        // In pretty mode, format long tuples with each element on a new line
21494        // Only expand if total width exceeds threshold
21495        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21496            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21497            for expr in &tuple.expressions {
21498                expr_strings.push(self.generate_to_string(expr)?);
21499            }
21500            self.too_wide(&expr_strings)
21501        } else {
21502            false
21503        };
21504
21505        if expand_tuple {
21506            self.write("(");
21507            self.write_newline();
21508            self.indent_level += 1;
21509            for (i, expr) in tuple.expressions.iter().enumerate() {
21510                if i > 0 {
21511                    self.write(",");
21512                    self.write_newline();
21513                }
21514                self.write_indent();
21515                self.generate_expression(expr)?;
21516            }
21517            self.indent_level -= 1;
21518            self.write_newline();
21519            self.write_indent();
21520            self.write(")");
21521        } else {
21522            self.write("(");
21523            for (i, expr) in tuple.expressions.iter().enumerate() {
21524                if i > 0 {
21525                    self.write(", ");
21526                }
21527                self.generate_expression(expr)?;
21528            }
21529            self.write(")");
21530        }
21531        Ok(())
21532    }
21533
21534    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21535        self.generate_expression(&pipe.this)?;
21536        self.write(" |> ");
21537        self.generate_expression(&pipe.expression)?;
21538        Ok(())
21539    }
21540
21541    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21542        self.generate_expression(&ordered.this)?;
21543        if ordered.desc {
21544            self.write_space();
21545            self.write_keyword("DESC");
21546        } else if ordered.explicit_asc {
21547            self.write_space();
21548            self.write_keyword("ASC");
21549        }
21550        if let Some(nulls_first) = ordered.nulls_first {
21551            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21552            // for the dialect. Different dialects have different NULL ordering defaults:
21553            //
21554            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21555            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21556            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21557            //
21558            // nulls_are_small (Spark, Hive, BigQuery, most others):
21559            //   - ASC: NULLS FIRST is default
21560            //   - DESC: NULLS LAST is default
21561            //
21562            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21563            //   - NULLS LAST is always the default regardless of sort direction
21564            let is_asc = !ordered.desc;
21565            let is_nulls_are_large = matches!(
21566                self.config.dialect,
21567                Some(DialectType::Oracle)
21568                    | Some(DialectType::PostgreSQL)
21569                    | Some(DialectType::Redshift)
21570                    | Some(DialectType::Snowflake)
21571            );
21572            let is_nulls_are_last = matches!(
21573                self.config.dialect,
21574                Some(DialectType::Dremio)
21575                    | Some(DialectType::DuckDB)
21576                    | Some(DialectType::Presto)
21577                    | Some(DialectType::Trino)
21578                    | Some(DialectType::Athena)
21579                    | Some(DialectType::ClickHouse)
21580                    | Some(DialectType::Drill)
21581                    | Some(DialectType::Exasol)
21582            );
21583
21584            // Check if the NULLS ordering matches the default for this dialect
21585            let is_default_nulls = if is_nulls_are_large {
21586                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21587                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21588            } else if is_nulls_are_last {
21589                // For nulls_are_last: NULLS LAST is always default
21590                !nulls_first
21591            } else {
21592                false
21593            };
21594
21595            if !is_default_nulls {
21596                self.write_space();
21597                self.write_keyword("NULLS");
21598                self.write_space();
21599                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21600            }
21601        }
21602        // WITH FILL clause (ClickHouse)
21603        if let Some(ref with_fill) = ordered.with_fill {
21604            self.write_space();
21605            self.generate_with_fill(with_fill)?;
21606        }
21607        Ok(())
21608    }
21609
21610    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21611    fn write_clickhouse_type(&mut self, type_str: &str) {
21612        if self.clickhouse_nullable_depth < 0 {
21613            // Map key context: don't wrap in Nullable
21614            self.write(type_str);
21615        } else {
21616            self.write(&format!("Nullable({})", type_str));
21617        }
21618    }
21619
21620    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21621        use crate::dialects::DialectType;
21622
21623        match dt {
21624            DataType::Boolean => {
21625                // Dialect-specific boolean type mappings
21626                match self.config.dialect {
21627                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21628                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21629                    Some(DialectType::Oracle) => {
21630                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21631                        self.write_keyword("NUMBER(1)")
21632                    }
21633                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21634                    _ => self.write_keyword("BOOLEAN"),
21635                }
21636            }
21637            DataType::TinyInt { length } => {
21638                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21639                // Dremio maps TINYINT to INT
21640                // ClickHouse maps TINYINT to Int8
21641                match self.config.dialect {
21642                    Some(DialectType::PostgreSQL)
21643                    | Some(DialectType::Redshift)
21644                    | Some(DialectType::Oracle)
21645                    | Some(DialectType::Exasol) => {
21646                        self.write_keyword("SMALLINT");
21647                    }
21648                    Some(DialectType::Teradata) => {
21649                        // Teradata uses BYTEINT for smallest integer
21650                        self.write_keyword("BYTEINT");
21651                    }
21652                    Some(DialectType::Dremio) => {
21653                        // Dremio maps TINYINT to INT
21654                        self.write_keyword("INT");
21655                    }
21656                    Some(DialectType::ClickHouse) => {
21657                        self.write_clickhouse_type("Int8");
21658                    }
21659                    _ => {
21660                        self.write_keyword("TINYINT");
21661                    }
21662                }
21663                if let Some(n) = length {
21664                    if !matches!(
21665                        self.config.dialect,
21666                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21667                    ) {
21668                        self.write(&format!("({})", n));
21669                    }
21670                }
21671            }
21672            DataType::SmallInt { length } => {
21673                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21674                match self.config.dialect {
21675                    Some(DialectType::Dremio) => {
21676                        self.write_keyword("INT");
21677                    }
21678                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21679                        self.write_keyword("INTEGER");
21680                    }
21681                    Some(DialectType::BigQuery) => {
21682                        self.write_keyword("INT64");
21683                    }
21684                    Some(DialectType::ClickHouse) => {
21685                        self.write_clickhouse_type("Int16");
21686                    }
21687                    _ => {
21688                        self.write_keyword("SMALLINT");
21689                        if let Some(n) = length {
21690                            self.write(&format!("({})", n));
21691                        }
21692                    }
21693                }
21694            }
21695            DataType::Int {
21696                length,
21697                integer_spelling,
21698            } => {
21699                // BigQuery uses INT64 for INT
21700                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21701                    self.write_keyword("INT64");
21702                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21703                    self.write_clickhouse_type("Int32");
21704                } else {
21705                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21706                    let use_integer = match self.config.dialect {
21707                        Some(DialectType::TSQL)
21708                        | Some(DialectType::Fabric)
21709                        | Some(DialectType::Presto)
21710                        | Some(DialectType::Trino)
21711                        | Some(DialectType::SQLite)
21712                        | Some(DialectType::Redshift) => true,
21713                        // Databricks preserves the original spelling
21714                        Some(DialectType::Databricks) => *integer_spelling,
21715                        _ => false,
21716                    };
21717                    if use_integer {
21718                        self.write_keyword("INTEGER");
21719                    } else {
21720                        self.write_keyword("INT");
21721                    }
21722                    if let Some(n) = length {
21723                        self.write(&format!("({})", n));
21724                    }
21725                }
21726            }
21727            DataType::BigInt { length } => {
21728                // Dialect-specific bigint type mappings
21729                match self.config.dialect {
21730                    Some(DialectType::Oracle) => {
21731                        // Oracle doesn't have BIGINT, uses INT
21732                        self.write_keyword("INT");
21733                    }
21734                    Some(DialectType::ClickHouse) => {
21735                        self.write_clickhouse_type("Int64");
21736                    }
21737                    _ => {
21738                        self.write_keyword("BIGINT");
21739                        if let Some(n) = length {
21740                            self.write(&format!("({})", n));
21741                        }
21742                    }
21743                }
21744            }
21745            DataType::Float {
21746                precision,
21747                scale,
21748                real_spelling,
21749            } => {
21750                // Dialect-specific float type mappings
21751                // If real_spelling is true, preserve REAL; otherwise use dialect default
21752                // Spark/Hive don't support REAL, always use FLOAT
21753                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21754                    self.write_clickhouse_type("Float32");
21755                } else if *real_spelling
21756                    && !matches!(
21757                        self.config.dialect,
21758                        Some(DialectType::Spark)
21759                            | Some(DialectType::Databricks)
21760                            | Some(DialectType::Hive)
21761                            | Some(DialectType::Snowflake)
21762                            | Some(DialectType::MySQL)
21763                            | Some(DialectType::BigQuery)
21764                    )
21765                {
21766                    self.write_keyword("REAL")
21767                } else {
21768                    match self.config.dialect {
21769                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21770                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21771                        _ => self.write_keyword("FLOAT"),
21772                    }
21773                }
21774                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21775                // Spark/Hive don't support FLOAT(precision)
21776                if !matches!(
21777                    self.config.dialect,
21778                    Some(DialectType::Spark)
21779                        | Some(DialectType::Databricks)
21780                        | Some(DialectType::Hive)
21781                        | Some(DialectType::Presto)
21782                        | Some(DialectType::Trino)
21783                ) {
21784                    if let Some(p) = precision {
21785                        self.write(&format!("({}", p));
21786                        if let Some(s) = scale {
21787                            self.write(&format!(", {})", s));
21788                        } else {
21789                            self.write(")");
21790                        }
21791                    }
21792                }
21793            }
21794            DataType::Double { precision, scale } => {
21795                // Dialect-specific double type mappings
21796                match self.config.dialect {
21797                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21798                        self.write_keyword("FLOAT")
21799                    } // SQL Server/Fabric FLOAT is double
21800                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21801                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21802                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21803                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21804                    Some(DialectType::PostgreSQL)
21805                    | Some(DialectType::Redshift)
21806                    | Some(DialectType::Teradata)
21807                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21808                    _ => self.write_keyword("DOUBLE"),
21809                }
21810                // MySQL supports DOUBLE(precision, scale)
21811                if let Some(p) = precision {
21812                    self.write(&format!("({}", p));
21813                    if let Some(s) = scale {
21814                        self.write(&format!(", {})", s));
21815                    } else {
21816                        self.write(")");
21817                    }
21818                }
21819            }
21820            DataType::Decimal { precision, scale } => {
21821                // Dialect-specific decimal type mappings
21822                match self.config.dialect {
21823                    Some(DialectType::ClickHouse) => {
21824                        self.write("Decimal");
21825                        if let Some(p) = precision {
21826                            self.write(&format!("({}", p));
21827                            if let Some(s) = scale {
21828                                self.write(&format!(", {}", s));
21829                            }
21830                            self.write(")");
21831                        }
21832                    }
21833                    Some(DialectType::Oracle) => {
21834                        // Oracle uses NUMBER instead of DECIMAL
21835                        self.write_keyword("NUMBER");
21836                        if let Some(p) = precision {
21837                            self.write(&format!("({}", p));
21838                            if let Some(s) = scale {
21839                                self.write(&format!(", {}", s));
21840                            }
21841                            self.write(")");
21842                        }
21843                    }
21844                    Some(DialectType::BigQuery) => {
21845                        // BigQuery uses NUMERIC instead of DECIMAL
21846                        self.write_keyword("NUMERIC");
21847                        if let Some(p) = precision {
21848                            self.write(&format!("({}", p));
21849                            if let Some(s) = scale {
21850                                self.write(&format!(", {}", s));
21851                            }
21852                            self.write(")");
21853                        }
21854                    }
21855                    _ => {
21856                        self.write_keyword("DECIMAL");
21857                        if let Some(p) = precision {
21858                            self.write(&format!("({}", p));
21859                            if let Some(s) = scale {
21860                                self.write(&format!(", {}", s));
21861                            }
21862                            self.write(")");
21863                        }
21864                    }
21865                }
21866            }
21867            DataType::Char { length } => {
21868                // Dialect-specific char type mappings
21869                match self.config.dialect {
21870                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21871                        // DuckDB/SQLite maps CHAR to TEXT
21872                        self.write_keyword("TEXT");
21873                    }
21874                    Some(DialectType::Hive)
21875                    | Some(DialectType::Spark)
21876                    | Some(DialectType::Databricks) => {
21877                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21878                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21879                        if length.is_some()
21880                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21881                        {
21882                            self.write_keyword("CHAR");
21883                            if let Some(n) = length {
21884                                self.write(&format!("({})", n));
21885                            }
21886                        } else {
21887                            self.write_keyword("STRING");
21888                        }
21889                    }
21890                    Some(DialectType::Dremio) => {
21891                        // Dremio maps CHAR to VARCHAR
21892                        self.write_keyword("VARCHAR");
21893                        if let Some(n) = length {
21894                            self.write(&format!("({})", n));
21895                        }
21896                    }
21897                    _ => {
21898                        self.write_keyword("CHAR");
21899                        if let Some(n) = length {
21900                            self.write(&format!("({})", n));
21901                        }
21902                    }
21903                }
21904            }
21905            DataType::VarChar {
21906                length,
21907                parenthesized_length,
21908            } => {
21909                // Dialect-specific varchar type mappings
21910                match self.config.dialect {
21911                    Some(DialectType::Oracle) => {
21912                        self.write_keyword("VARCHAR2");
21913                        if let Some(n) = length {
21914                            self.write(&format!("({})", n));
21915                        }
21916                    }
21917                    Some(DialectType::DuckDB) => {
21918                        // DuckDB maps VARCHAR to TEXT, preserving length
21919                        self.write_keyword("TEXT");
21920                        if let Some(n) = length {
21921                            self.write(&format!("({})", n));
21922                        }
21923                    }
21924                    Some(DialectType::SQLite) => {
21925                        // SQLite maps VARCHAR to TEXT, preserving length
21926                        self.write_keyword("TEXT");
21927                        if let Some(n) = length {
21928                            self.write(&format!("({})", n));
21929                        }
21930                    }
21931                    Some(DialectType::MySQL) if length.is_none() => {
21932                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21933                        self.write_keyword("TEXT");
21934                    }
21935                    Some(DialectType::Hive)
21936                    | Some(DialectType::Spark)
21937                    | Some(DialectType::Databricks)
21938                        if length.is_none() =>
21939                    {
21940                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21941                        self.write_keyword("STRING");
21942                    }
21943                    _ => {
21944                        self.write_keyword("VARCHAR");
21945                        if let Some(n) = length {
21946                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21947                            if *parenthesized_length {
21948                                self.write(&format!("(({}))", n));
21949                            } else {
21950                                self.write(&format!("({})", n));
21951                            }
21952                        }
21953                    }
21954                }
21955            }
21956            DataType::Text => {
21957                // Dialect-specific text type mappings
21958                match self.config.dialect {
21959                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21960                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21961                        self.write_keyword("VARCHAR(MAX)")
21962                    }
21963                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21964                    Some(DialectType::Snowflake)
21965                    | Some(DialectType::Dremio)
21966                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21967                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21968                    Some(DialectType::Presto)
21969                    | Some(DialectType::Trino)
21970                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21971                    Some(DialectType::Spark)
21972                    | Some(DialectType::Databricks)
21973                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21974                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21975                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21976                        self.write_keyword("STRING")
21977                    }
21978                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21979                    _ => self.write_keyword("TEXT"),
21980                }
21981            }
21982            DataType::TextWithLength { length } => {
21983                // TEXT(n) - dialect-specific type with length
21984                match self.config.dialect {
21985                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21986                    Some(DialectType::Hive)
21987                    | Some(DialectType::Spark)
21988                    | Some(DialectType::Databricks) => {
21989                        self.write(&format!("VARCHAR({})", length));
21990                    }
21991                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21992                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21993                    Some(DialectType::Snowflake)
21994                    | Some(DialectType::Presto)
21995                    | Some(DialectType::Trino)
21996                    | Some(DialectType::Athena)
21997                    | Some(DialectType::Drill)
21998                    | Some(DialectType::Dremio) => {
21999                        self.write(&format!("VARCHAR({})", length));
22000                    }
22001                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22002                        self.write(&format!("VARCHAR({})", length))
22003                    }
22004                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22005                        self.write(&format!("STRING({})", length))
22006                    }
22007                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22008                    _ => self.write(&format!("TEXT({})", length)),
22009                }
22010            }
22011            DataType::String { length } => {
22012                // STRING type with optional length (BigQuery STRING(n))
22013                match self.config.dialect {
22014                    Some(DialectType::ClickHouse) => {
22015                        // ClickHouse uses String with specific casing
22016                        self.write("String");
22017                        if let Some(n) = length {
22018                            self.write(&format!("({})", n));
22019                        }
22020                    }
22021                    Some(DialectType::BigQuery)
22022                    | Some(DialectType::Hive)
22023                    | Some(DialectType::Spark)
22024                    | Some(DialectType::Databricks)
22025                    | Some(DialectType::StarRocks)
22026                    | Some(DialectType::Doris) => {
22027                        self.write_keyword("STRING");
22028                        if let Some(n) = length {
22029                            self.write(&format!("({})", n));
22030                        }
22031                    }
22032                    Some(DialectType::PostgreSQL) => {
22033                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
22034                        if let Some(n) = length {
22035                            self.write_keyword("VARCHAR");
22036                            self.write(&format!("({})", n));
22037                        } else {
22038                            self.write_keyword("TEXT");
22039                        }
22040                    }
22041                    Some(DialectType::Redshift) => {
22042                        // Redshift: STRING -> VARCHAR(MAX)
22043                        if let Some(n) = length {
22044                            self.write_keyword("VARCHAR");
22045                            self.write(&format!("({})", n));
22046                        } else {
22047                            self.write_keyword("VARCHAR(MAX)");
22048                        }
22049                    }
22050                    Some(DialectType::MySQL) => {
22051                        // MySQL doesn't have STRING - use VARCHAR or TEXT
22052                        if let Some(n) = length {
22053                            self.write_keyword("VARCHAR");
22054                            self.write(&format!("({})", n));
22055                        } else {
22056                            self.write_keyword("TEXT");
22057                        }
22058                    }
22059                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22060                        // TSQL: STRING -> VARCHAR(MAX)
22061                        if let Some(n) = length {
22062                            self.write_keyword("VARCHAR");
22063                            self.write(&format!("({})", n));
22064                        } else {
22065                            self.write_keyword("VARCHAR(MAX)");
22066                        }
22067                    }
22068                    Some(DialectType::Oracle) => {
22069                        // Oracle: STRING -> CLOB
22070                        self.write_keyword("CLOB");
22071                    }
22072                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22073                        // DuckDB/Materialize uses TEXT for string types
22074                        self.write_keyword("TEXT");
22075                        if let Some(n) = length {
22076                            self.write(&format!("({})", n));
22077                        }
22078                    }
22079                    Some(DialectType::Presto)
22080                    | Some(DialectType::Trino)
22081                    | Some(DialectType::Drill)
22082                    | Some(DialectType::Dremio) => {
22083                        // Presto/Trino/Drill use VARCHAR for string types
22084                        self.write_keyword("VARCHAR");
22085                        if let Some(n) = length {
22086                            self.write(&format!("({})", n));
22087                        }
22088                    }
22089                    Some(DialectType::Snowflake) => {
22090                        // Snowflake: STRING stays as STRING (identity/DDL)
22091                        // CAST context STRING -> VARCHAR is handled in generate_cast
22092                        self.write_keyword("STRING");
22093                        if let Some(n) = length {
22094                            self.write(&format!("({})", n));
22095                        }
22096                    }
22097                    _ => {
22098                        // Default: output STRING with optional length
22099                        self.write_keyword("STRING");
22100                        if let Some(n) = length {
22101                            self.write(&format!("({})", n));
22102                        }
22103                    }
22104                }
22105            }
22106            DataType::Binary { length } => {
22107                // Dialect-specific binary type mappings
22108                match self.config.dialect {
22109                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22110                        self.write_keyword("BYTEA");
22111                        if let Some(n) = length {
22112                            self.write(&format!("({})", n));
22113                        }
22114                    }
22115                    Some(DialectType::Redshift) => {
22116                        self.write_keyword("VARBYTE");
22117                        if let Some(n) = length {
22118                            self.write(&format!("({})", n));
22119                        }
22120                    }
22121                    Some(DialectType::DuckDB)
22122                    | Some(DialectType::SQLite)
22123                    | Some(DialectType::Oracle) => {
22124                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22125                        self.write_keyword("BLOB");
22126                        if let Some(n) = length {
22127                            self.write(&format!("({})", n));
22128                        }
22129                    }
22130                    Some(DialectType::Presto)
22131                    | Some(DialectType::Trino)
22132                    | Some(DialectType::Athena)
22133                    | Some(DialectType::Drill)
22134                    | Some(DialectType::Dremio) => {
22135                        // These dialects map BINARY to VARBINARY
22136                        self.write_keyword("VARBINARY");
22137                        if let Some(n) = length {
22138                            self.write(&format!("({})", n));
22139                        }
22140                    }
22141                    Some(DialectType::ClickHouse) => {
22142                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22143                        if self.clickhouse_nullable_depth < 0 {
22144                            self.write("BINARY");
22145                        } else {
22146                            self.write("Nullable(BINARY");
22147                        }
22148                        if let Some(n) = length {
22149                            self.write(&format!("({})", n));
22150                        }
22151                        if self.clickhouse_nullable_depth >= 0 {
22152                            self.write(")");
22153                        }
22154                    }
22155                    _ => {
22156                        self.write_keyword("BINARY");
22157                        if let Some(n) = length {
22158                            self.write(&format!("({})", n));
22159                        }
22160                    }
22161                }
22162            }
22163            DataType::VarBinary { length } => {
22164                // Dialect-specific varbinary type mappings
22165                match self.config.dialect {
22166                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22167                        self.write_keyword("BYTEA");
22168                        if let Some(n) = length {
22169                            self.write(&format!("({})", n));
22170                        }
22171                    }
22172                    Some(DialectType::Redshift) => {
22173                        self.write_keyword("VARBYTE");
22174                        if let Some(n) = length {
22175                            self.write(&format!("({})", n));
22176                        }
22177                    }
22178                    Some(DialectType::DuckDB)
22179                    | Some(DialectType::SQLite)
22180                    | Some(DialectType::Oracle) => {
22181                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22182                        self.write_keyword("BLOB");
22183                        if let Some(n) = length {
22184                            self.write(&format!("({})", n));
22185                        }
22186                    }
22187                    Some(DialectType::Exasol) => {
22188                        // Exasol maps VARBINARY to VARCHAR
22189                        self.write_keyword("VARCHAR");
22190                    }
22191                    Some(DialectType::Spark)
22192                    | Some(DialectType::Hive)
22193                    | Some(DialectType::Databricks) => {
22194                        // Spark/Hive use BINARY instead of VARBINARY
22195                        self.write_keyword("BINARY");
22196                        if let Some(n) = length {
22197                            self.write(&format!("({})", n));
22198                        }
22199                    }
22200                    Some(DialectType::ClickHouse) => {
22201                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22202                        self.write_clickhouse_type("String");
22203                    }
22204                    _ => {
22205                        self.write_keyword("VARBINARY");
22206                        if let Some(n) = length {
22207                            self.write(&format!("({})", n));
22208                        }
22209                    }
22210                }
22211            }
22212            DataType::Blob => {
22213                // Dialect-specific blob type mappings
22214                match self.config.dialect {
22215                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22216                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22217                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22218                        self.write_keyword("VARBINARY")
22219                    }
22220                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22221                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22222                    Some(DialectType::Presto)
22223                    | Some(DialectType::Trino)
22224                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22225                    Some(DialectType::DuckDB) => {
22226                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22227                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22228                        self.write_keyword("VARBINARY");
22229                    }
22230                    Some(DialectType::Spark)
22231                    | Some(DialectType::Databricks)
22232                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22233                    Some(DialectType::ClickHouse) => {
22234                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22235                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22236                        // This matches Python sqlglot behavior.
22237                        self.write("Nullable(String)");
22238                    }
22239                    _ => self.write_keyword("BLOB"),
22240                }
22241            }
22242            DataType::Bit { length } => {
22243                // Dialect-specific bit type mappings
22244                match self.config.dialect {
22245                    Some(DialectType::Dremio)
22246                    | Some(DialectType::Spark)
22247                    | Some(DialectType::Databricks)
22248                    | Some(DialectType::Hive)
22249                    | Some(DialectType::Snowflake)
22250                    | Some(DialectType::BigQuery)
22251                    | Some(DialectType::Presto)
22252                    | Some(DialectType::Trino)
22253                    | Some(DialectType::ClickHouse)
22254                    | Some(DialectType::Redshift) => {
22255                        // These dialects don't support BIT type, use BOOLEAN
22256                        self.write_keyword("BOOLEAN");
22257                    }
22258                    _ => {
22259                        self.write_keyword("BIT");
22260                        if let Some(n) = length {
22261                            self.write(&format!("({})", n));
22262                        }
22263                    }
22264                }
22265            }
22266            DataType::VarBit { length } => {
22267                self.write_keyword("VARBIT");
22268                if let Some(n) = length {
22269                    self.write(&format!("({})", n));
22270                }
22271            }
22272            DataType::Date => self.write_keyword("DATE"),
22273            DataType::Time {
22274                precision,
22275                timezone,
22276            } => {
22277                if *timezone {
22278                    // Dialect-specific TIME WITH TIME ZONE output
22279                    match self.config.dialect {
22280                        Some(DialectType::DuckDB) => {
22281                            // DuckDB: TIMETZ (drops precision)
22282                            self.write_keyword("TIMETZ");
22283                        }
22284                        Some(DialectType::PostgreSQL) => {
22285                            // PostgreSQL: TIMETZ or TIMETZ(p)
22286                            self.write_keyword("TIMETZ");
22287                            if let Some(p) = precision {
22288                                self.write(&format!("({})", p));
22289                            }
22290                        }
22291                        _ => {
22292                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22293                            self.write_keyword("TIME");
22294                            if let Some(p) = precision {
22295                                self.write(&format!("({})", p));
22296                            }
22297                            self.write_keyword(" WITH TIME ZONE");
22298                        }
22299                    }
22300                } else {
22301                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22302                    if matches!(
22303                        self.config.dialect,
22304                        Some(DialectType::Spark)
22305                            | Some(DialectType::Databricks)
22306                            | Some(DialectType::Hive)
22307                    ) {
22308                        self.write_keyword("TIMESTAMP");
22309                    } else {
22310                        self.write_keyword("TIME");
22311                        if let Some(p) = precision {
22312                            self.write(&format!("({})", p));
22313                        }
22314                    }
22315                }
22316            }
22317            DataType::Timestamp {
22318                precision,
22319                timezone,
22320            } => {
22321                // Dialect-specific timestamp type mappings
22322                match self.config.dialect {
22323                    Some(DialectType::ClickHouse) => {
22324                        self.write("DateTime");
22325                        if let Some(p) = precision {
22326                            self.write(&format!("({})", p));
22327                        }
22328                    }
22329                    Some(DialectType::TSQL) => {
22330                        if *timezone {
22331                            self.write_keyword("DATETIMEOFFSET");
22332                        } else {
22333                            self.write_keyword("DATETIME2");
22334                        }
22335                        if let Some(p) = precision {
22336                            self.write(&format!("({})", p));
22337                        }
22338                    }
22339                    Some(DialectType::MySQL) => {
22340                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22341                        self.write_keyword("TIMESTAMP");
22342                        if let Some(p) = precision {
22343                            self.write(&format!("({})", p));
22344                        }
22345                    }
22346                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22347                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22348                        self.write_keyword("DATETIME");
22349                        if let Some(p) = precision {
22350                            self.write(&format!("({})", p));
22351                        }
22352                    }
22353                    Some(DialectType::BigQuery) => {
22354                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22355                        if *timezone {
22356                            self.write_keyword("TIMESTAMP");
22357                        } else {
22358                            self.write_keyword("DATETIME");
22359                        }
22360                    }
22361                    Some(DialectType::DuckDB) => {
22362                        // DuckDB: TIMESTAMPTZ shorthand
22363                        if *timezone {
22364                            self.write_keyword("TIMESTAMPTZ");
22365                        } else {
22366                            self.write_keyword("TIMESTAMP");
22367                            if let Some(p) = precision {
22368                                self.write(&format!("({})", p));
22369                            }
22370                        }
22371                    }
22372                    _ => {
22373                        if *timezone && !self.config.tz_to_with_time_zone {
22374                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22375                            self.write_keyword("TIMESTAMPTZ");
22376                            if let Some(p) = precision {
22377                                self.write(&format!("({})", p));
22378                            }
22379                        } else {
22380                            self.write_keyword("TIMESTAMP");
22381                            if let Some(p) = precision {
22382                                self.write(&format!("({})", p));
22383                            }
22384                            if *timezone {
22385                                self.write_space();
22386                                self.write_keyword("WITH TIME ZONE");
22387                            }
22388                        }
22389                    }
22390                }
22391            }
22392            DataType::Interval { unit, to } => {
22393                self.write_keyword("INTERVAL");
22394                if let Some(u) = unit {
22395                    self.write_space();
22396                    self.write_keyword(u);
22397                }
22398                // Handle range intervals like DAY TO HOUR
22399                if let Some(t) = to {
22400                    self.write_space();
22401                    self.write_keyword("TO");
22402                    self.write_space();
22403                    self.write_keyword(t);
22404                }
22405            }
22406            DataType::Json => {
22407                // Dialect-specific JSON type mappings
22408                match self.config.dialect {
22409                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22410                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22411                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22412                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22413                    _ => self.write_keyword("JSON"),
22414                }
22415            }
22416            DataType::JsonB => {
22417                // JSONB is PostgreSQL specific, but Doris also supports it
22418                match self.config.dialect {
22419                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22420                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22421                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22422                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22423                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22424                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22425                }
22426            }
22427            DataType::Uuid => {
22428                // Dialect-specific UUID type mappings
22429                match self.config.dialect {
22430                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22431                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22432                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22433                    Some(DialectType::BigQuery)
22434                    | Some(DialectType::Spark)
22435                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22436                    _ => self.write_keyword("UUID"),
22437                }
22438            }
22439            DataType::Array {
22440                element_type,
22441                dimension,
22442            } => {
22443                // Dialect-specific array syntax
22444                match self.config.dialect {
22445                    Some(DialectType::PostgreSQL)
22446                    | Some(DialectType::Redshift)
22447                    | Some(DialectType::DuckDB) => {
22448                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22449                        self.generate_data_type(element_type)?;
22450                        if let Some(dim) = dimension {
22451                            self.write(&format!("[{}]", dim));
22452                        } else {
22453                            self.write("[]");
22454                        }
22455                    }
22456                    Some(DialectType::BigQuery) => {
22457                        self.write_keyword("ARRAY<");
22458                        self.generate_data_type(element_type)?;
22459                        self.write(">");
22460                    }
22461                    Some(DialectType::Snowflake)
22462                    | Some(DialectType::Presto)
22463                    | Some(DialectType::Trino)
22464                    | Some(DialectType::ClickHouse) => {
22465                        // These dialects use Array(TYPE) parentheses syntax
22466                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22467                            self.write("Array(");
22468                        } else {
22469                            self.write_keyword("ARRAY(");
22470                        }
22471                        self.generate_data_type(element_type)?;
22472                        self.write(")");
22473                    }
22474                    Some(DialectType::TSQL)
22475                    | Some(DialectType::MySQL)
22476                    | Some(DialectType::Oracle) => {
22477                        // These dialects don't have native array types
22478                        // Fall back to JSON or use native workarounds
22479                        match self.config.dialect {
22480                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22481                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22482                            _ => self.write_keyword("JSON"),
22483                        }
22484                    }
22485                    _ => {
22486                        // Default: use angle bracket syntax (ARRAY<T>)
22487                        self.write_keyword("ARRAY<");
22488                        self.generate_data_type(element_type)?;
22489                        self.write(">");
22490                    }
22491                }
22492            }
22493            DataType::List { element_type } => {
22494                // Materialize: element_type LIST (postfix syntax)
22495                self.generate_data_type(element_type)?;
22496                self.write_keyword(" LIST");
22497            }
22498            DataType::Map {
22499                key_type,
22500                value_type,
22501            } => {
22502                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22503                match self.config.dialect {
22504                    Some(DialectType::Materialize) => {
22505                        // Materialize: MAP[key_type => value_type]
22506                        self.write_keyword("MAP[");
22507                        self.generate_data_type(key_type)?;
22508                        self.write(" => ");
22509                        self.generate_data_type(value_type)?;
22510                        self.write("]");
22511                    }
22512                    Some(DialectType::Snowflake)
22513                    | Some(DialectType::RisingWave)
22514                    | Some(DialectType::DuckDB)
22515                    | Some(DialectType::Presto)
22516                    | Some(DialectType::Trino)
22517                    | Some(DialectType::Athena) => {
22518                        self.write_keyword("MAP(");
22519                        self.generate_data_type(key_type)?;
22520                        self.write(", ");
22521                        self.generate_data_type(value_type)?;
22522                        self.write(")");
22523                    }
22524                    Some(DialectType::ClickHouse) => {
22525                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22526                        // Key types must NOT be wrapped in Nullable
22527                        self.write("Map(");
22528                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22529                        self.generate_data_type(key_type)?;
22530                        self.clickhouse_nullable_depth = 0;
22531                        self.write(", ");
22532                        self.generate_data_type(value_type)?;
22533                        self.write(")");
22534                    }
22535                    _ => {
22536                        self.write_keyword("MAP<");
22537                        self.generate_data_type(key_type)?;
22538                        self.write(", ");
22539                        self.generate_data_type(value_type)?;
22540                        self.write(">");
22541                    }
22542                }
22543            }
22544            DataType::Vector {
22545                element_type,
22546                dimension,
22547            } => {
22548                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22549                    // SingleStore format: VECTOR(dimension, type_alias)
22550                    self.write_keyword("VECTOR(");
22551                    if let Some(dim) = dimension {
22552                        self.write(&dim.to_string());
22553                    }
22554                    // Map type back to SingleStore alias
22555                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22556                        DataType::TinyInt { .. } => Some("I8"),
22557                        DataType::SmallInt { .. } => Some("I16"),
22558                        DataType::Int { .. } => Some("I32"),
22559                        DataType::BigInt { .. } => Some("I64"),
22560                        DataType::Float { .. } => Some("F32"),
22561                        DataType::Double { .. } => Some("F64"),
22562                        _ => None,
22563                    });
22564                    if let Some(alias) = type_alias {
22565                        if dimension.is_some() {
22566                            self.write(", ");
22567                        }
22568                        self.write(alias);
22569                    }
22570                    self.write(")");
22571                } else {
22572                    // Snowflake format: VECTOR(type, dimension)
22573                    self.write_keyword("VECTOR(");
22574                    if let Some(ref et) = element_type {
22575                        self.generate_data_type(et)?;
22576                        if dimension.is_some() {
22577                            self.write(", ");
22578                        }
22579                    }
22580                    if let Some(dim) = dimension {
22581                        self.write(&dim.to_string());
22582                    }
22583                    self.write(")");
22584                }
22585            }
22586            DataType::Object { fields, modifier } => {
22587                self.write_keyword("OBJECT(");
22588                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22589                    if i > 0 {
22590                        self.write(", ");
22591                    }
22592                    self.write(name);
22593                    self.write(" ");
22594                    self.generate_data_type(dt)?;
22595                    if *not_null {
22596                        self.write_keyword(" NOT NULL");
22597                    }
22598                }
22599                self.write(")");
22600                if let Some(mod_str) = modifier {
22601                    self.write(" ");
22602                    self.write_keyword(mod_str);
22603                }
22604            }
22605            DataType::Struct { fields, nested } => {
22606                // Dialect-specific struct type mappings
22607                match self.config.dialect {
22608                    Some(DialectType::Snowflake) => {
22609                        // Snowflake maps STRUCT to OBJECT
22610                        self.write_keyword("OBJECT(");
22611                        for (i, field) in fields.iter().enumerate() {
22612                            if i > 0 {
22613                                self.write(", ");
22614                            }
22615                            if !field.name.is_empty() {
22616                                self.write(&field.name);
22617                                self.write(" ");
22618                            }
22619                            self.generate_data_type(&field.data_type)?;
22620                        }
22621                        self.write(")");
22622                    }
22623                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22624                        // Presto/Trino use ROW(name TYPE, ...) syntax
22625                        self.write_keyword("ROW(");
22626                        for (i, field) in fields.iter().enumerate() {
22627                            if i > 0 {
22628                                self.write(", ");
22629                            }
22630                            if !field.name.is_empty() {
22631                                self.write(&field.name);
22632                                self.write(" ");
22633                            }
22634                            self.generate_data_type(&field.data_type)?;
22635                        }
22636                        self.write(")");
22637                    }
22638                    Some(DialectType::DuckDB) => {
22639                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22640                        self.write_keyword("STRUCT(");
22641                        for (i, field) in fields.iter().enumerate() {
22642                            if i > 0 {
22643                                self.write(", ");
22644                            }
22645                            if !field.name.is_empty() {
22646                                self.write(&field.name);
22647                                self.write(" ");
22648                            }
22649                            self.generate_data_type(&field.data_type)?;
22650                        }
22651                        self.write(")");
22652                    }
22653                    Some(DialectType::ClickHouse) => {
22654                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22655                        self.write("Tuple(");
22656                        for (i, field) in fields.iter().enumerate() {
22657                            if i > 0 {
22658                                self.write(", ");
22659                            }
22660                            if !field.name.is_empty() {
22661                                self.write(&field.name);
22662                                self.write(" ");
22663                            }
22664                            self.generate_data_type(&field.data_type)?;
22665                        }
22666                        self.write(")");
22667                    }
22668                    Some(DialectType::SingleStore) => {
22669                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22670                        self.write_keyword("RECORD(");
22671                        for (i, field) in fields.iter().enumerate() {
22672                            if i > 0 {
22673                                self.write(", ");
22674                            }
22675                            if !field.name.is_empty() {
22676                                self.write(&field.name);
22677                                self.write(" ");
22678                            }
22679                            self.generate_data_type(&field.data_type)?;
22680                        }
22681                        self.write(")");
22682                    }
22683                    _ => {
22684                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22685                        let force_angle_brackets = matches!(
22686                            self.config.dialect,
22687                            Some(DialectType::Hive)
22688                                | Some(DialectType::Spark)
22689                                | Some(DialectType::Databricks)
22690                        );
22691                        if *nested && !force_angle_brackets {
22692                            self.write_keyword("STRUCT(");
22693                            for (i, field) in fields.iter().enumerate() {
22694                                if i > 0 {
22695                                    self.write(", ");
22696                                }
22697                                if !field.name.is_empty() {
22698                                    self.write(&field.name);
22699                                    self.write(" ");
22700                                }
22701                                self.generate_data_type(&field.data_type)?;
22702                            }
22703                            self.write(")");
22704                        } else {
22705                            self.write_keyword("STRUCT<");
22706                            for (i, field) in fields.iter().enumerate() {
22707                                if i > 0 {
22708                                    self.write(", ");
22709                                }
22710                                if !field.name.is_empty() {
22711                                    // Named field: name TYPE (with configurable separator for Hive)
22712                                    self.write(&field.name);
22713                                    self.write(self.config.struct_field_sep);
22714                                }
22715                                // For anonymous fields, just output the type
22716                                self.generate_data_type(&field.data_type)?;
22717                                // Spark/Databricks: Output COMMENT clause if present
22718                                if let Some(comment) = &field.comment {
22719                                    self.write(" COMMENT '");
22720                                    self.write(comment);
22721                                    self.write("'");
22722                                }
22723                                // BigQuery: Output OPTIONS clause if present
22724                                if !field.options.is_empty() {
22725                                    self.write(" ");
22726                                    self.generate_options_clause(&field.options)?;
22727                                }
22728                            }
22729                            self.write(">");
22730                        }
22731                    }
22732                }
22733            }
22734            DataType::Enum {
22735                values,
22736                assignments,
22737            } => {
22738                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22739                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22740                if self.config.dialect == Some(DialectType::ClickHouse) {
22741                    self.write("Enum(");
22742                } else {
22743                    self.write_keyword("ENUM(");
22744                }
22745                for (i, val) in values.iter().enumerate() {
22746                    if i > 0 {
22747                        self.write(", ");
22748                    }
22749                    self.write("'");
22750                    self.write(val);
22751                    self.write("'");
22752                    if let Some(Some(assignment)) = assignments.get(i) {
22753                        self.write(" = ");
22754                        self.write(assignment);
22755                    }
22756                }
22757                self.write(")");
22758            }
22759            DataType::Set { values } => {
22760                // MySQL SET type: SET('a', 'b', 'c')
22761                self.write_keyword("SET(");
22762                for (i, val) in values.iter().enumerate() {
22763                    if i > 0 {
22764                        self.write(", ");
22765                    }
22766                    self.write("'");
22767                    self.write(val);
22768                    self.write("'");
22769                }
22770                self.write(")");
22771            }
22772            DataType::Union { fields } => {
22773                // DuckDB UNION type: UNION(num INT, str TEXT)
22774                self.write_keyword("UNION(");
22775                for (i, (name, dt)) in fields.iter().enumerate() {
22776                    if i > 0 {
22777                        self.write(", ");
22778                    }
22779                    if !name.is_empty() {
22780                        self.write(name);
22781                        self.write(" ");
22782                    }
22783                    self.generate_data_type(dt)?;
22784                }
22785                self.write(")");
22786            }
22787            DataType::Nullable { inner } => {
22788                // ClickHouse: Nullable(T), other dialects: just the inner type
22789                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22790                    self.write("Nullable(");
22791                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22792                    let saved_depth = self.clickhouse_nullable_depth;
22793                    self.clickhouse_nullable_depth = -1;
22794                    self.generate_data_type(inner)?;
22795                    self.clickhouse_nullable_depth = saved_depth;
22796                    self.write(")");
22797                } else {
22798                    // Map ClickHouse-specific custom type names to standard types
22799                    match inner.as_ref() {
22800                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22801                            self.generate_data_type(&DataType::Timestamp {
22802                                precision: None,
22803                                timezone: false,
22804                            })?;
22805                        }
22806                        _ => {
22807                            self.generate_data_type(inner)?;
22808                        }
22809                    }
22810                }
22811            }
22812            DataType::Custom { name } => {
22813                // Handle dialect-specific type transformations
22814                let name_upper = name.to_uppercase();
22815                match self.config.dialect {
22816                    Some(DialectType::ClickHouse) => {
22817                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22818                            (name_upper[..idx].to_string(), &name[idx..])
22819                        } else {
22820                            (name_upper.clone(), "")
22821                        };
22822                        let mapped = match base_upper.as_str() {
22823                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22824                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22825                            "DATETIME64" => "DateTime64",
22826                            "DATE32" => "Date32",
22827                            "INT" => "Int32",
22828                            "MEDIUMINT" => "Int32",
22829                            "INT8" => "Int8",
22830                            "INT16" => "Int16",
22831                            "INT32" => "Int32",
22832                            "INT64" => "Int64",
22833                            "INT128" => "Int128",
22834                            "INT256" => "Int256",
22835                            "UINT8" => "UInt8",
22836                            "UINT16" => "UInt16",
22837                            "UINT32" => "UInt32",
22838                            "UINT64" => "UInt64",
22839                            "UINT128" => "UInt128",
22840                            "UINT256" => "UInt256",
22841                            "FLOAT32" => "Float32",
22842                            "FLOAT64" => "Float64",
22843                            "DECIMAL32" => "Decimal32",
22844                            "DECIMAL64" => "Decimal64",
22845                            "DECIMAL128" => "Decimal128",
22846                            "DECIMAL256" => "Decimal256",
22847                            "ENUM" => "Enum",
22848                            "ENUM8" => "Enum8",
22849                            "ENUM16" => "Enum16",
22850                            "FIXEDSTRING" => "FixedString",
22851                            "NESTED" => "Nested",
22852                            "LOWCARDINALITY" => "LowCardinality",
22853                            "NULLABLE" => "Nullable",
22854                            "IPV4" => "IPv4",
22855                            "IPV6" => "IPv6",
22856                            "POINT" => "Point",
22857                            "RING" => "Ring",
22858                            "LINESTRING" => "LineString",
22859                            "MULTILINESTRING" => "MultiLineString",
22860                            "POLYGON" => "Polygon",
22861                            "MULTIPOLYGON" => "MultiPolygon",
22862                            "AGGREGATEFUNCTION" => "AggregateFunction",
22863                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22864                            "DYNAMIC" => "Dynamic",
22865                            _ => "",
22866                        };
22867                        if mapped.is_empty() {
22868                            self.write(name);
22869                        } else {
22870                            self.write(mapped);
22871                            self.write(suffix);
22872                        }
22873                    }
22874                    Some(DialectType::MySQL)
22875                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22876                    {
22877                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22878                        self.write_keyword("TIMESTAMP");
22879                    }
22880                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22881                        self.write_keyword("SQL_VARIANT");
22882                    }
22883                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22884                        self.write_keyword("DECIMAL(38, 5)");
22885                    }
22886                    Some(DialectType::Exasol) => {
22887                        // Exasol type mappings for custom types
22888                        match name_upper.as_str() {
22889                            // Binary types → VARCHAR
22890                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22891                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22892                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22893                            // Integer types
22894                            "MEDIUMINT" => self.write_keyword("INT"),
22895                            // Decimal types → DECIMAL
22896                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22897                                self.write_keyword("DECIMAL")
22898                            }
22899                            // Timestamp types
22900                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22901                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22902                            _ => self.write(name),
22903                        }
22904                    }
22905                    Some(DialectType::Dremio) => {
22906                        // Dremio type mappings for custom types
22907                        match name_upper.as_str() {
22908                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22909                            "ARRAY" => self.write_keyword("LIST"),
22910                            "NCHAR" => self.write_keyword("VARCHAR"),
22911                            _ => self.write(name),
22912                        }
22913                    }
22914                    // Map dialect-specific custom types to standard SQL types for other dialects
22915                    _ => {
22916                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22917                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22918                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22919                        } else {
22920                            (name_upper.clone(), None)
22921                        };
22922
22923                        match base_upper.as_str() {
22924                            "INT64"
22925                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22926                            {
22927                                self.write_keyword("BIGINT");
22928                            }
22929                            "FLOAT64"
22930                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22931                            {
22932                                self.write_keyword("DOUBLE");
22933                            }
22934                            "BOOL"
22935                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22936                            {
22937                                self.write_keyword("BOOLEAN");
22938                            }
22939                            "BYTES"
22940                                if matches!(
22941                                    self.config.dialect,
22942                                    Some(DialectType::Spark)
22943                                        | Some(DialectType::Hive)
22944                                        | Some(DialectType::Databricks)
22945                                ) =>
22946                            {
22947                                self.write_keyword("BINARY");
22948                            }
22949                            "BYTES"
22950                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22951                            {
22952                                self.write_keyword("VARBINARY");
22953                            }
22954                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22955                            "DATETIME2" | "SMALLDATETIME"
22956                                if !matches!(
22957                                    self.config.dialect,
22958                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22959                                ) =>
22960                            {
22961                                // PostgreSQL preserves precision, others don't
22962                                if matches!(
22963                                    self.config.dialect,
22964                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22965                                ) {
22966                                    self.write_keyword("TIMESTAMP");
22967                                    if let Some(args) = _args_str {
22968                                        self.write(args);
22969                                    }
22970                                } else {
22971                                    self.write_keyword("TIMESTAMP");
22972                                }
22973                            }
22974                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22975                            "DATETIMEOFFSET"
22976                                if !matches!(
22977                                    self.config.dialect,
22978                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22979                                ) =>
22980                            {
22981                                if matches!(
22982                                    self.config.dialect,
22983                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22984                                ) {
22985                                    self.write_keyword("TIMESTAMPTZ");
22986                                    if let Some(args) = _args_str {
22987                                        self.write(args);
22988                                    }
22989                                } else {
22990                                    self.write_keyword("TIMESTAMPTZ");
22991                                }
22992                            }
22993                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22994                            "UNIQUEIDENTIFIER"
22995                                if !matches!(
22996                                    self.config.dialect,
22997                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22998                                ) =>
22999                            {
23000                                match self.config.dialect {
23001                                    Some(DialectType::Spark)
23002                                    | Some(DialectType::Databricks)
23003                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23004                                    _ => self.write_keyword("UUID"),
23005                                }
23006                            }
23007                            // TSQL BIT -> BOOLEAN for most dialects
23008                            "BIT"
23009                                if !matches!(
23010                                    self.config.dialect,
23011                                    Some(DialectType::TSQL)
23012                                        | Some(DialectType::Fabric)
23013                                        | Some(DialectType::PostgreSQL)
23014                                        | Some(DialectType::MySQL)
23015                                        | Some(DialectType::DuckDB)
23016                                ) =>
23017                            {
23018                                self.write_keyword("BOOLEAN");
23019                            }
23020                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
23021                            "NVARCHAR"
23022                                if !matches!(
23023                                    self.config.dialect,
23024                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23025                                ) =>
23026                            {
23027                                match self.config.dialect {
23028                                    Some(DialectType::Oracle) => {
23029                                        // Oracle: NVARCHAR -> NVARCHAR2
23030                                        self.write_keyword("NVARCHAR2");
23031                                        if let Some(args) = _args_str {
23032                                            self.write(args);
23033                                        }
23034                                    }
23035                                    Some(DialectType::BigQuery) => {
23036                                        // BigQuery: NVARCHAR -> STRING
23037                                        self.write_keyword("STRING");
23038                                    }
23039                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23040                                        self.write_keyword("TEXT");
23041                                        if let Some(args) = _args_str {
23042                                            self.write(args);
23043                                        }
23044                                    }
23045                                    Some(DialectType::Hive) => {
23046                                        // Hive: NVARCHAR -> STRING
23047                                        self.write_keyword("STRING");
23048                                    }
23049                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23050                                        if _args_str.is_some() {
23051                                            self.write_keyword("VARCHAR");
23052                                            self.write(_args_str.unwrap());
23053                                        } else {
23054                                            self.write_keyword("STRING");
23055                                        }
23056                                    }
23057                                    _ => {
23058                                        self.write_keyword("VARCHAR");
23059                                        if let Some(args) = _args_str {
23060                                            self.write(args);
23061                                        }
23062                                    }
23063                                }
23064                            }
23065                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23066                            "NCHAR"
23067                                if !matches!(
23068                                    self.config.dialect,
23069                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23070                                ) =>
23071                            {
23072                                match self.config.dialect {
23073                                    Some(DialectType::Oracle) => {
23074                                        // Oracle natively supports NCHAR
23075                                        self.write_keyword("NCHAR");
23076                                        if let Some(args) = _args_str {
23077                                            self.write(args);
23078                                        }
23079                                    }
23080                                    Some(DialectType::BigQuery) => {
23081                                        // BigQuery: NCHAR -> STRING
23082                                        self.write_keyword("STRING");
23083                                    }
23084                                    Some(DialectType::Hive) => {
23085                                        // Hive: NCHAR -> STRING
23086                                        self.write_keyword("STRING");
23087                                    }
23088                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23089                                        self.write_keyword("TEXT");
23090                                        if let Some(args) = _args_str {
23091                                            self.write(args);
23092                                        }
23093                                    }
23094                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23095                                        if _args_str.is_some() {
23096                                            self.write_keyword("CHAR");
23097                                            self.write(_args_str.unwrap());
23098                                        } else {
23099                                            self.write_keyword("STRING");
23100                                        }
23101                                    }
23102                                    _ => {
23103                                        self.write_keyword("CHAR");
23104                                        if let Some(args) = _args_str {
23105                                            self.write(args);
23106                                        }
23107                                    }
23108                                }
23109                            }
23110                            // MySQL text variant types -> map to appropriate target type
23111                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23112                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23113                                Some(DialectType::MySQL)
23114                                | Some(DialectType::SingleStore)
23115                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23116                                Some(DialectType::Spark)
23117                                | Some(DialectType::Databricks)
23118                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23119                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23120                                Some(DialectType::Presto)
23121                                | Some(DialectType::Trino)
23122                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23123                                Some(DialectType::Snowflake)
23124                                | Some(DialectType::Redshift)
23125                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23126                                _ => self.write_keyword("TEXT"),
23127                            },
23128                            // MySQL blob variant types -> map to appropriate target type
23129                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23130                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23131                                Some(DialectType::MySQL)
23132                                | Some(DialectType::SingleStore)
23133                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23134                                Some(DialectType::Spark)
23135                                | Some(DialectType::Databricks)
23136                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23137                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23138                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23139                                Some(DialectType::Presto)
23140                                | Some(DialectType::Trino)
23141                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23142                                Some(DialectType::Snowflake)
23143                                | Some(DialectType::Redshift)
23144                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23145                                _ => self.write_keyword("BLOB"),
23146                            },
23147                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23148                            "LONGVARCHAR" => match self.config.dialect {
23149                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23150                                _ => self.write_keyword("VARCHAR"),
23151                            },
23152                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23153                            "DATETIME" => {
23154                                match self.config.dialect {
23155                                    Some(DialectType::MySQL)
23156                                    | Some(DialectType::Doris)
23157                                    | Some(DialectType::StarRocks)
23158                                    | Some(DialectType::TSQL)
23159                                    | Some(DialectType::Fabric)
23160                                    | Some(DialectType::BigQuery)
23161                                    | Some(DialectType::SQLite)
23162                                    | Some(DialectType::Snowflake) => {
23163                                        self.write_keyword("DATETIME");
23164                                        if let Some(args) = _args_str {
23165                                            self.write(args);
23166                                        }
23167                                    }
23168                                    Some(_) => {
23169                                        // Only map to TIMESTAMP when we have a specific target dialect
23170                                        self.write_keyword("TIMESTAMP");
23171                                        if let Some(args) = _args_str {
23172                                            self.write(args);
23173                                        }
23174                                    }
23175                                    None => {
23176                                        // No dialect - preserve original
23177                                        self.write(name);
23178                                    }
23179                                }
23180                            }
23181                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23182                            "VARCHAR2"
23183                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23184                            {
23185                                match self.config.dialect {
23186                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23187                                        self.write_keyword("TEXT");
23188                                    }
23189                                    Some(DialectType::Hive)
23190                                    | Some(DialectType::Spark)
23191                                    | Some(DialectType::Databricks)
23192                                    | Some(DialectType::BigQuery)
23193                                    | Some(DialectType::ClickHouse)
23194                                    | Some(DialectType::StarRocks)
23195                                    | Some(DialectType::Doris) => {
23196                                        self.write_keyword("STRING");
23197                                    }
23198                                    _ => {
23199                                        self.write_keyword("VARCHAR");
23200                                        if let Some(args) = _args_str {
23201                                            self.write(args);
23202                                        }
23203                                    }
23204                                }
23205                            }
23206                            "NVARCHAR2"
23207                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23208                            {
23209                                match self.config.dialect {
23210                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23211                                        self.write_keyword("TEXT");
23212                                    }
23213                                    Some(DialectType::Hive)
23214                                    | Some(DialectType::Spark)
23215                                    | Some(DialectType::Databricks)
23216                                    | Some(DialectType::BigQuery)
23217                                    | Some(DialectType::ClickHouse)
23218                                    | Some(DialectType::StarRocks)
23219                                    | Some(DialectType::Doris) => {
23220                                        self.write_keyword("STRING");
23221                                    }
23222                                    _ => {
23223                                        self.write_keyword("VARCHAR");
23224                                        if let Some(args) = _args_str {
23225                                            self.write(args);
23226                                        }
23227                                    }
23228                                }
23229                            }
23230                            _ => self.write(name),
23231                        }
23232                    }
23233                }
23234            }
23235            DataType::Geometry { subtype, srid } => {
23236                // Dialect-specific geometry type mappings
23237                match self.config.dialect {
23238                    Some(DialectType::MySQL) => {
23239                        // MySQL uses POINT SRID 4326 syntax for specific types
23240                        if let Some(sub) = subtype {
23241                            self.write_keyword(sub);
23242                            if let Some(s) = srid {
23243                                self.write(" SRID ");
23244                                self.write(&s.to_string());
23245                            }
23246                        } else {
23247                            self.write_keyword("GEOMETRY");
23248                        }
23249                    }
23250                    Some(DialectType::BigQuery) => {
23251                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23252                        self.write_keyword("GEOGRAPHY");
23253                    }
23254                    Some(DialectType::Teradata) => {
23255                        // Teradata uses ST_GEOMETRY
23256                        self.write_keyword("ST_GEOMETRY");
23257                        if subtype.is_some() || srid.is_some() {
23258                            self.write("(");
23259                            if let Some(sub) = subtype {
23260                                self.write_keyword(sub);
23261                            }
23262                            if let Some(s) = srid {
23263                                if subtype.is_some() {
23264                                    self.write(", ");
23265                                }
23266                                self.write(&s.to_string());
23267                            }
23268                            self.write(")");
23269                        }
23270                    }
23271                    _ => {
23272                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23273                        self.write_keyword("GEOMETRY");
23274                        if subtype.is_some() || srid.is_some() {
23275                            self.write("(");
23276                            if let Some(sub) = subtype {
23277                                self.write_keyword(sub);
23278                            }
23279                            if let Some(s) = srid {
23280                                if subtype.is_some() {
23281                                    self.write(", ");
23282                                }
23283                                self.write(&s.to_string());
23284                            }
23285                            self.write(")");
23286                        }
23287                    }
23288                }
23289            }
23290            DataType::Geography { subtype, srid } => {
23291                // Dialect-specific geography type mappings
23292                match self.config.dialect {
23293                    Some(DialectType::MySQL) => {
23294                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23295                        if let Some(sub) = subtype {
23296                            self.write_keyword(sub);
23297                        } else {
23298                            self.write_keyword("GEOMETRY");
23299                        }
23300                        // Geography implies SRID 4326 (WGS84)
23301                        let effective_srid = srid.unwrap_or(4326);
23302                        self.write(" SRID ");
23303                        self.write(&effective_srid.to_string());
23304                    }
23305                    Some(DialectType::BigQuery) => {
23306                        // BigQuery uses simple GEOGRAPHY without parameters
23307                        self.write_keyword("GEOGRAPHY");
23308                    }
23309                    Some(DialectType::Snowflake) => {
23310                        // Snowflake uses GEOGRAPHY without parameters
23311                        self.write_keyword("GEOGRAPHY");
23312                    }
23313                    _ => {
23314                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23315                        self.write_keyword("GEOGRAPHY");
23316                        if subtype.is_some() || srid.is_some() {
23317                            self.write("(");
23318                            if let Some(sub) = subtype {
23319                                self.write_keyword(sub);
23320                            }
23321                            if let Some(s) = srid {
23322                                if subtype.is_some() {
23323                                    self.write(", ");
23324                                }
23325                                self.write(&s.to_string());
23326                            }
23327                            self.write(")");
23328                        }
23329                    }
23330                }
23331            }
23332            DataType::CharacterSet { name } => {
23333                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23334                self.write_keyword("CHAR CHARACTER SET ");
23335                self.write(name);
23336            }
23337            _ => self.write("UNKNOWN"),
23338        }
23339        Ok(())
23340    }
23341
23342    // === Helper methods ===
23343
23344    fn write(&mut self, s: &str) {
23345        self.output.push_str(s);
23346    }
23347
23348    fn write_space(&mut self) {
23349        self.output.push(' ');
23350    }
23351
23352    fn write_keyword(&mut self, keyword: &str) {
23353        if self.config.uppercase_keywords {
23354            self.output.push_str(keyword);
23355        } else {
23356            self.output.push_str(&keyword.to_lowercase());
23357        }
23358    }
23359
23360    /// Write a function name respecting the normalize_functions config setting
23361    fn write_func_name(&mut self, name: &str) {
23362        let normalized = self.normalize_func_name(name);
23363        self.output.push_str(&normalized);
23364    }
23365
23366    /// Convert strptime format string to Exasol format string
23367    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23368    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23369    fn convert_strptime_to_exasol_format(format: &str) -> String {
23370        let mut result = String::new();
23371        let chars: Vec<char> = format.chars().collect();
23372        let mut i = 0;
23373        while i < chars.len() {
23374            if chars[i] == '%' && i + 1 < chars.len() {
23375                let spec = chars[i + 1];
23376                let exasol_spec = match spec {
23377                    'Y' => "YYYY",
23378                    'y' => "YY",
23379                    'm' => "MM",
23380                    'd' => "DD",
23381                    'H' => "HH",
23382                    'M' => "MI",
23383                    'S' => "SS",
23384                    'a' => "DY",    // abbreviated weekday name
23385                    'A' => "DAY",   // full weekday name
23386                    'b' => "MON",   // abbreviated month name
23387                    'B' => "MONTH", // full month name
23388                    'I' => "H12",   // 12-hour format
23389                    'u' => "ID",    // ISO weekday (1-7)
23390                    'V' => "IW",    // ISO week number
23391                    'G' => "IYYY",  // ISO year
23392                    'W' => "UW",    // Week number (Monday as first day)
23393                    'U' => "UW",    // Week number (Sunday as first day)
23394                    'z' => "Z",     // timezone offset
23395                    _ => {
23396                        // Unknown specifier, keep as-is
23397                        result.push('%');
23398                        result.push(spec);
23399                        i += 2;
23400                        continue;
23401                    }
23402                };
23403                result.push_str(exasol_spec);
23404                i += 2;
23405            } else {
23406                result.push(chars[i]);
23407                i += 1;
23408            }
23409        }
23410        result
23411    }
23412
23413    /// Convert strptime format string to PostgreSQL/Redshift format string
23414    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23415    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23416    fn convert_strptime_to_postgres_format(format: &str) -> String {
23417        let mut result = String::new();
23418        let chars: Vec<char> = format.chars().collect();
23419        let mut i = 0;
23420        while i < chars.len() {
23421            if chars[i] == '%' && i + 1 < chars.len() {
23422                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23423                if chars[i + 1] == '-' && i + 2 < chars.len() {
23424                    let spec = chars[i + 2];
23425                    let pg_spec = match spec {
23426                        'd' => "FMDD",
23427                        'm' => "FMMM",
23428                        'H' => "FMHH24",
23429                        'M' => "FMMI",
23430                        'S' => "FMSS",
23431                        _ => {
23432                            result.push('%');
23433                            result.push('-');
23434                            result.push(spec);
23435                            i += 3;
23436                            continue;
23437                        }
23438                    };
23439                    result.push_str(pg_spec);
23440                    i += 3;
23441                    continue;
23442                }
23443                let spec = chars[i + 1];
23444                let pg_spec = match spec {
23445                    'Y' => "YYYY",
23446                    'y' => "YY",
23447                    'm' => "MM",
23448                    'd' => "DD",
23449                    'H' => "HH24",
23450                    'I' => "HH12",
23451                    'M' => "MI",
23452                    'S' => "SS",
23453                    'f' => "US",      // microseconds
23454                    'u' => "D",       // day of week (1=Monday)
23455                    'j' => "DDD",     // day of year
23456                    'z' => "OF",      // UTC offset
23457                    'Z' => "TZ",      // timezone name
23458                    'A' => "TMDay",   // full weekday name
23459                    'a' => "TMDy",    // abbreviated weekday name
23460                    'b' => "TMMon",   // abbreviated month name
23461                    'B' => "TMMonth", // full month name
23462                    'U' => "WW",      // week number
23463                    _ => {
23464                        // Unknown specifier, keep as-is
23465                        result.push('%');
23466                        result.push(spec);
23467                        i += 2;
23468                        continue;
23469                    }
23470                };
23471                result.push_str(pg_spec);
23472                i += 2;
23473            } else {
23474                result.push(chars[i]);
23475                i += 1;
23476            }
23477        }
23478        result
23479    }
23480
23481    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23482    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23483        if self.config.limit_only_literals {
23484            if let Some(value) = Self::try_evaluate_constant(expr) {
23485                self.write(&value.to_string());
23486                return Ok(());
23487            }
23488        }
23489        self.generate_expression(expr)
23490    }
23491
23492    /// Format a comment with proper spacing.
23493    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23494    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23495    fn write_formatted_comment(&mut self, comment: &str) {
23496        // Normalize all comments to block comment format /* ... */
23497        // This matches Python sqlglot behavior which always outputs block comments
23498        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23499            // Already block comment - extract inner content
23500            // Preserve internal whitespace, but ensure at least one space padding
23501            &comment[2..comment.len() - 2]
23502        } else if comment.starts_with("--") {
23503            // Line comment - extract content after --
23504            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23505            &comment[2..]
23506        } else {
23507            // Raw content (no delimiters)
23508            comment
23509        };
23510        // Skip empty comments (e.g., bare "--" with no content)
23511        if content.trim().is_empty() {
23512            return;
23513        }
23514        // Ensure at least one space after /* and before */
23515        self.output.push_str("/*");
23516        if !content.starts_with(' ') {
23517            self.output.push(' ');
23518        }
23519        self.output.push_str(content);
23520        if !content.ends_with(' ') {
23521            self.output.push(' ');
23522        }
23523        self.output.push_str("*/");
23524    }
23525
23526    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23527    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23528    fn escape_block_for_single_quote(&self, block: &str) -> String {
23529        let escape_backslash = matches!(
23530            self.config.dialect,
23531            Some(crate::dialects::DialectType::Snowflake)
23532        );
23533        let mut escaped = String::with_capacity(block.len() + 4);
23534        for ch in block.chars() {
23535            if ch == '\'' {
23536                escaped.push('\\');
23537                escaped.push('\'');
23538            } else if escape_backslash && ch == '\\' {
23539                escaped.push('\\');
23540                escaped.push('\\');
23541            } else {
23542                escaped.push(ch);
23543            }
23544        }
23545        escaped
23546    }
23547
23548    fn write_newline(&mut self) {
23549        self.output.push('\n');
23550    }
23551
23552    fn write_indent(&mut self) {
23553        for _ in 0..self.indent_level {
23554            self.output.push_str(&self.config.indent);
23555        }
23556    }
23557
23558    // === SQLGlot-style pretty printing helpers ===
23559
23560    /// Returns the separator string for pretty printing.
23561    /// Check if the total length of arguments exceeds max_text_width.
23562    /// Used for dynamic line breaking in expressions() formatting.
23563    fn too_wide(&self, args: &[String]) -> bool {
23564        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23565    }
23566
23567    /// Generate an expression to a string using a temporary non-pretty generator.
23568    /// Useful for width calculations before deciding on formatting.
23569    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23570        let config = GeneratorConfig {
23571            pretty: false,
23572            dialect: self.config.dialect,
23573            ..Default::default()
23574        };
23575        let mut gen = Generator::with_config(config);
23576        gen.generate_expression(expr)?;
23577        Ok(gen.output)
23578    }
23579
23580    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23581    /// In pretty mode: newline + indented keyword + newline + indented condition
23582    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23583        if self.config.pretty {
23584            self.write_newline();
23585            self.write_indent();
23586            self.write_keyword(keyword);
23587            self.write_newline();
23588            self.indent_level += 1;
23589            self.write_indent();
23590            self.generate_expression(condition)?;
23591            self.indent_level -= 1;
23592        } else {
23593            self.write_space();
23594            self.write_keyword(keyword);
23595            self.write_space();
23596            self.generate_expression(condition)?;
23597        }
23598        Ok(())
23599    }
23600
23601    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23602    /// In pretty mode: each expression on new line with indentation
23603    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23604        if exprs.is_empty() {
23605            return Ok(());
23606        }
23607
23608        if self.config.pretty {
23609            self.write_newline();
23610            self.write_indent();
23611            self.write_keyword(keyword);
23612            self.write_newline();
23613            self.indent_level += 1;
23614            for (i, expr) in exprs.iter().enumerate() {
23615                if i > 0 {
23616                    self.write(",");
23617                    self.write_newline();
23618                }
23619                self.write_indent();
23620                self.generate_expression(expr)?;
23621            }
23622            self.indent_level -= 1;
23623        } else {
23624            self.write_space();
23625            self.write_keyword(keyword);
23626            self.write_space();
23627            for (i, expr) in exprs.iter().enumerate() {
23628                if i > 0 {
23629                    self.write(", ");
23630                }
23631                self.generate_expression(expr)?;
23632            }
23633        }
23634        Ok(())
23635    }
23636
23637    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23638    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23639        if orderings.is_empty() {
23640            return Ok(());
23641        }
23642
23643        if self.config.pretty {
23644            self.write_newline();
23645            self.write_indent();
23646            self.write_keyword(keyword);
23647            self.write_newline();
23648            self.indent_level += 1;
23649            for (i, ordered) in orderings.iter().enumerate() {
23650                if i > 0 {
23651                    self.write(",");
23652                    self.write_newline();
23653                }
23654                self.write_indent();
23655                self.generate_ordered(ordered)?;
23656            }
23657            self.indent_level -= 1;
23658        } else {
23659            self.write_space();
23660            self.write_keyword(keyword);
23661            self.write_space();
23662            for (i, ordered) in orderings.iter().enumerate() {
23663                if i > 0 {
23664                    self.write(", ");
23665                }
23666                self.generate_ordered(ordered)?;
23667            }
23668        }
23669        Ok(())
23670    }
23671
23672    /// Writes WINDOW clause with named window definitions
23673    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23674        if windows.is_empty() {
23675            return Ok(());
23676        }
23677
23678        if self.config.pretty {
23679            self.write_newline();
23680            self.write_indent();
23681            self.write_keyword("WINDOW");
23682            self.write_newline();
23683            self.indent_level += 1;
23684            for (i, named_window) in windows.iter().enumerate() {
23685                if i > 0 {
23686                    self.write(",");
23687                    self.write_newline();
23688                }
23689                self.write_indent();
23690                self.generate_identifier(&named_window.name)?;
23691                self.write_space();
23692                self.write_keyword("AS");
23693                self.write(" (");
23694                self.generate_over(&named_window.spec)?;
23695                self.write(")");
23696            }
23697            self.indent_level -= 1;
23698        } else {
23699            self.write_space();
23700            self.write_keyword("WINDOW");
23701            self.write_space();
23702            for (i, named_window) in windows.iter().enumerate() {
23703                if i > 0 {
23704                    self.write(", ");
23705                }
23706                self.generate_identifier(&named_window.name)?;
23707                self.write_space();
23708                self.write_keyword("AS");
23709                self.write(" (");
23710                self.generate_over(&named_window.spec)?;
23711                self.write(")");
23712            }
23713        }
23714        Ok(())
23715    }
23716
23717    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23718    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23719        // AI_AGG(this, expression)
23720        self.write_keyword("AI_AGG");
23721        self.write("(");
23722        self.generate_expression(&e.this)?;
23723        self.write(", ");
23724        self.generate_expression(&e.expression)?;
23725        self.write(")");
23726        Ok(())
23727    }
23728
23729    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23730        // AI_CLASSIFY(input, [categories], [config])
23731        self.write_keyword("AI_CLASSIFY");
23732        self.write("(");
23733        self.generate_expression(&e.this)?;
23734        if let Some(categories) = &e.categories {
23735            self.write(", ");
23736            self.generate_expression(categories)?;
23737        }
23738        if let Some(config) = &e.config {
23739            self.write(", ");
23740            self.generate_expression(config)?;
23741        }
23742        self.write(")");
23743        Ok(())
23744    }
23745
23746    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23747        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23748        self.write_keyword("ADD");
23749        self.write_space();
23750        if e.exists {
23751            self.write_keyword("IF NOT EXISTS");
23752            self.write_space();
23753        }
23754        self.generate_expression(&e.this)?;
23755        if let Some(location) = &e.location {
23756            self.write_space();
23757            self.generate_expression(location)?;
23758        }
23759        Ok(())
23760    }
23761
23762    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23763        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23764        self.write_keyword("ALGORITHM");
23765        self.write("=");
23766        self.generate_expression(&e.this)?;
23767        Ok(())
23768    }
23769
23770    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23771        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23772        self.generate_expression(&e.this)?;
23773        self.write_space();
23774        self.write_keyword("AS");
23775        self.write(" (");
23776        for (i, expr) in e.expressions.iter().enumerate() {
23777            if i > 0 {
23778                self.write(", ");
23779            }
23780            self.generate_expression(expr)?;
23781        }
23782        self.write(")");
23783        Ok(())
23784    }
23785
23786    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23787        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23788        self.write_keyword("ALLOWED_VALUES");
23789        self.write_space();
23790        for (i, expr) in e.expressions.iter().enumerate() {
23791            if i > 0 {
23792                self.write(", ");
23793            }
23794            self.generate_expression(expr)?;
23795        }
23796        Ok(())
23797    }
23798
23799    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23800        // Python: complex logic based on dtype, default, comment, visible, etc.
23801        self.write_keyword("ALTER COLUMN");
23802        self.write_space();
23803        self.generate_expression(&e.this)?;
23804
23805        if let Some(dtype) = &e.dtype {
23806            self.write_space();
23807            self.write_keyword("SET DATA TYPE");
23808            self.write_space();
23809            self.generate_expression(dtype)?;
23810            if let Some(collate) = &e.collate {
23811                self.write_space();
23812                self.write_keyword("COLLATE");
23813                self.write_space();
23814                self.generate_expression(collate)?;
23815            }
23816            if let Some(using) = &e.using {
23817                self.write_space();
23818                self.write_keyword("USING");
23819                self.write_space();
23820                self.generate_expression(using)?;
23821            }
23822        } else if let Some(default) = &e.default {
23823            self.write_space();
23824            self.write_keyword("SET DEFAULT");
23825            self.write_space();
23826            self.generate_expression(default)?;
23827        } else if let Some(comment) = &e.comment {
23828            self.write_space();
23829            self.write_keyword("COMMENT");
23830            self.write_space();
23831            self.generate_expression(comment)?;
23832        } else if let Some(drop) = &e.drop {
23833            self.write_space();
23834            self.write_keyword("DROP");
23835            self.write_space();
23836            self.generate_expression(drop)?;
23837        } else if let Some(visible) = &e.visible {
23838            self.write_space();
23839            self.generate_expression(visible)?;
23840        } else if let Some(rename_to) = &e.rename_to {
23841            self.write_space();
23842            self.write_keyword("RENAME TO");
23843            self.write_space();
23844            self.generate_expression(rename_to)?;
23845        } else if let Some(allow_null) = &e.allow_null {
23846            self.write_space();
23847            self.generate_expression(allow_null)?;
23848        }
23849        Ok(())
23850    }
23851
23852    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23853        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23854        self.write_keyword("ALTER SESSION");
23855        self.write_space();
23856        if e.unset.is_some() {
23857            self.write_keyword("UNSET");
23858        } else {
23859            self.write_keyword("SET");
23860        }
23861        self.write_space();
23862        for (i, expr) in e.expressions.iter().enumerate() {
23863            if i > 0 {
23864                self.write(", ");
23865            }
23866            self.generate_expression(expr)?;
23867        }
23868        Ok(())
23869    }
23870
23871    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23872        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23873        self.write_keyword("SET");
23874
23875        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23876        if let Some(opt) = &e.option {
23877            self.write_space();
23878            self.generate_expression(opt)?;
23879        }
23880
23881        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23882        // Check if expressions look like property assignments
23883        if !e.expressions.is_empty() {
23884            // Check if this looks like property assignments (for SET PROPERTIES)
23885            let is_properties = e
23886                .expressions
23887                .iter()
23888                .any(|expr| matches!(expr, Expression::Eq(_)));
23889            if is_properties && e.option.is_none() {
23890                self.write_space();
23891                self.write_keyword("PROPERTIES");
23892            }
23893            self.write_space();
23894            for (i, expr) in e.expressions.iter().enumerate() {
23895                if i > 0 {
23896                    self.write(", ");
23897                }
23898                self.generate_expression(expr)?;
23899            }
23900        }
23901
23902        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23903        if let Some(file_format) = &e.file_format {
23904            self.write(" ");
23905            self.write_keyword("STAGE_FILE_FORMAT");
23906            self.write(" = (");
23907            self.generate_space_separated_properties(file_format)?;
23908            self.write(")");
23909        }
23910
23911        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23912        if let Some(copy_options) = &e.copy_options {
23913            self.write(" ");
23914            self.write_keyword("STAGE_COPY_OPTIONS");
23915            self.write(" = (");
23916            self.generate_space_separated_properties(copy_options)?;
23917            self.write(")");
23918        }
23919
23920        // Generate TAG ...
23921        if let Some(tag) = &e.tag {
23922            self.write(" ");
23923            self.write_keyword("TAG");
23924            self.write(" ");
23925            self.generate_expression(tag)?;
23926        }
23927
23928        Ok(())
23929    }
23930
23931    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23932    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23933        match expr {
23934            Expression::Tuple(t) => {
23935                for (i, prop) in t.expressions.iter().enumerate() {
23936                    if i > 0 {
23937                        self.write(" ");
23938                    }
23939                    self.generate_expression(prop)?;
23940                }
23941            }
23942            _ => {
23943                self.generate_expression(expr)?;
23944            }
23945        }
23946        Ok(())
23947    }
23948
23949    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23950        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23951        self.write_keyword("ALTER");
23952        if e.compound.is_some() {
23953            self.write_space();
23954            self.write_keyword("COMPOUND");
23955        }
23956        self.write_space();
23957        self.write_keyword("SORTKEY");
23958        self.write_space();
23959        if let Some(this) = &e.this {
23960            self.generate_expression(this)?;
23961        } else if !e.expressions.is_empty() {
23962            self.write("(");
23963            for (i, expr) in e.expressions.iter().enumerate() {
23964                if i > 0 {
23965                    self.write(", ");
23966                }
23967                self.generate_expression(expr)?;
23968            }
23969            self.write(")");
23970        }
23971        Ok(())
23972    }
23973
23974    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23975        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23976        self.write_keyword("ANALYZE");
23977        if !e.options.is_empty() {
23978            self.write_space();
23979            for (i, opt) in e.options.iter().enumerate() {
23980                if i > 0 {
23981                    self.write_space();
23982                }
23983                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23984                if let Expression::Identifier(id) = opt {
23985                    self.write_keyword(&id.name);
23986                } else {
23987                    self.generate_expression(opt)?;
23988                }
23989            }
23990        }
23991        if let Some(kind) = &e.kind {
23992            self.write_space();
23993            self.write_keyword(kind);
23994        }
23995        if let Some(this) = &e.this {
23996            self.write_space();
23997            self.generate_expression(this)?;
23998        }
23999        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
24000        if !e.columns.is_empty() {
24001            self.write("(");
24002            for (i, col) in e.columns.iter().enumerate() {
24003                if i > 0 {
24004                    self.write(", ");
24005                }
24006                self.write(col);
24007            }
24008            self.write(")");
24009        }
24010        if let Some(partition) = &e.partition {
24011            self.write_space();
24012            self.generate_expression(partition)?;
24013        }
24014        if let Some(mode) = &e.mode {
24015            self.write_space();
24016            self.generate_expression(mode)?;
24017        }
24018        if let Some(expression) = &e.expression {
24019            self.write_space();
24020            self.generate_expression(expression)?;
24021        }
24022        if !e.properties.is_empty() {
24023            self.write_space();
24024            self.write_keyword(self.config.with_properties_prefix);
24025            self.write(" (");
24026            for (i, prop) in e.properties.iter().enumerate() {
24027                if i > 0 {
24028                    self.write(", ");
24029                }
24030                self.generate_expression(prop)?;
24031            }
24032            self.write(")");
24033        }
24034        Ok(())
24035    }
24036
24037    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
24038        // Python: return f"DELETE{kind} STATISTICS"
24039        self.write_keyword("DELETE");
24040        if let Some(kind) = &e.kind {
24041            self.write_space();
24042            self.write_keyword(kind);
24043        }
24044        self.write_space();
24045        self.write_keyword("STATISTICS");
24046        Ok(())
24047    }
24048
24049    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
24050        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
24051        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
24052        if let Expression::Identifier(id) = e.this.as_ref() {
24053            self.write_keyword(&id.name);
24054        } else {
24055            self.generate_expression(&e.this)?;
24056        }
24057        self.write_space();
24058        self.write_keyword("HISTOGRAM ON");
24059        self.write_space();
24060        for (i, expr) in e.expressions.iter().enumerate() {
24061            if i > 0 {
24062                self.write(", ");
24063            }
24064            self.generate_expression(expr)?;
24065        }
24066        if let Some(expression) = &e.expression {
24067            self.write_space();
24068            self.generate_expression(expression)?;
24069        }
24070        if let Some(update_options) = &e.update_options {
24071            self.write_space();
24072            self.generate_expression(update_options)?;
24073            self.write_space();
24074            self.write_keyword("UPDATE");
24075        }
24076        Ok(())
24077    }
24078
24079    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24080        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24081        self.write_keyword("LIST CHAINED ROWS");
24082        if let Some(expression) = &e.expression {
24083            self.write_space();
24084            self.write_keyword("INTO");
24085            self.write_space();
24086            self.generate_expression(expression)?;
24087        }
24088        Ok(())
24089    }
24090
24091    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24092        // Python: return f"SAMPLE {sample} {kind}"
24093        self.write_keyword("SAMPLE");
24094        self.write_space();
24095        if let Some(sample) = &e.sample {
24096            self.generate_expression(sample)?;
24097            self.write_space();
24098        }
24099        self.write_keyword(&e.kind);
24100        Ok(())
24101    }
24102
24103    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24104        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24105        self.write_keyword(&e.kind);
24106        if let Some(option) = &e.option {
24107            self.write_space();
24108            self.generate_expression(option)?;
24109        }
24110        self.write_space();
24111        self.write_keyword("STATISTICS");
24112        if let Some(this) = &e.this {
24113            self.write_space();
24114            self.generate_expression(this)?;
24115        }
24116        if !e.expressions.is_empty() {
24117            self.write_space();
24118            for (i, expr) in e.expressions.iter().enumerate() {
24119                if i > 0 {
24120                    self.write(", ");
24121                }
24122                self.generate_expression(expr)?;
24123            }
24124        }
24125        Ok(())
24126    }
24127
24128    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24129        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24130        self.write_keyword("VALIDATE");
24131        self.write_space();
24132        self.write_keyword(&e.kind);
24133        if let Some(this) = &e.this {
24134            self.write_space();
24135            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24136            if let Expression::Identifier(id) = this.as_ref() {
24137                self.write_keyword(&id.name);
24138            } else {
24139                self.generate_expression(this)?;
24140            }
24141        }
24142        if let Some(expression) = &e.expression {
24143            self.write_space();
24144            self.write_keyword("INTO");
24145            self.write_space();
24146            self.generate_expression(expression)?;
24147        }
24148        Ok(())
24149    }
24150
24151    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24152        // Python: return f"WITH {expressions}"
24153        self.write_keyword("WITH");
24154        self.write_space();
24155        for (i, expr) in e.expressions.iter().enumerate() {
24156            if i > 0 {
24157                self.write(", ");
24158            }
24159            self.generate_expression(expr)?;
24160        }
24161        Ok(())
24162    }
24163
24164    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24165        // Anonymous represents a generic function call: FUNC_NAME(args...)
24166        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24167        self.generate_expression(&e.this)?;
24168        self.write("(");
24169        for (i, arg) in e.expressions.iter().enumerate() {
24170            if i > 0 {
24171                self.write(", ");
24172            }
24173            self.generate_expression(arg)?;
24174        }
24175        self.write(")");
24176        Ok(())
24177    }
24178
24179    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24180        // Same as Anonymous but for aggregate functions
24181        self.generate_expression(&e.this)?;
24182        self.write("(");
24183        for (i, arg) in e.expressions.iter().enumerate() {
24184            if i > 0 {
24185                self.write(", ");
24186            }
24187            self.generate_expression(arg)?;
24188        }
24189        self.write(")");
24190        Ok(())
24191    }
24192
24193    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24194        // Python: return f"{this} APPLY({expr})"
24195        self.generate_expression(&e.this)?;
24196        self.write_space();
24197        self.write_keyword("APPLY");
24198        self.write("(");
24199        self.generate_expression(&e.expression)?;
24200        self.write(")");
24201        Ok(())
24202    }
24203
24204    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24205        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24206        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24207        self.write("(");
24208        self.generate_expression(&e.this)?;
24209        if let Some(percentile) = &e.percentile {
24210            self.write(", ");
24211            self.generate_expression(percentile)?;
24212        }
24213        self.write(")");
24214        Ok(())
24215    }
24216
24217    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24218        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24219        self.write_keyword("APPROX_QUANTILE");
24220        self.write("(");
24221        self.generate_expression(&e.this)?;
24222        if let Some(quantile) = &e.quantile {
24223            self.write(", ");
24224            self.generate_expression(quantile)?;
24225        }
24226        if let Some(accuracy) = &e.accuracy {
24227            self.write(", ");
24228            self.generate_expression(accuracy)?;
24229        }
24230        if let Some(weight) = &e.weight {
24231            self.write(", ");
24232            self.generate_expression(weight)?;
24233        }
24234        self.write(")");
24235        Ok(())
24236    }
24237
24238    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24239        // APPROX_QUANTILES(this, expression)
24240        self.write_keyword("APPROX_QUANTILES");
24241        self.write("(");
24242        self.generate_expression(&e.this)?;
24243        if let Some(expression) = &e.expression {
24244            self.write(", ");
24245            self.generate_expression(expression)?;
24246        }
24247        self.write(")");
24248        Ok(())
24249    }
24250
24251    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24252        // APPROX_TOP_K(this[, expression][, counters])
24253        self.write_keyword("APPROX_TOP_K");
24254        self.write("(");
24255        self.generate_expression(&e.this)?;
24256        if let Some(expression) = &e.expression {
24257            self.write(", ");
24258            self.generate_expression(expression)?;
24259        }
24260        if let Some(counters) = &e.counters {
24261            self.write(", ");
24262            self.generate_expression(counters)?;
24263        }
24264        self.write(")");
24265        Ok(())
24266    }
24267
24268    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24269        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24270        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24271        self.write("(");
24272        self.generate_expression(&e.this)?;
24273        if let Some(expression) = &e.expression {
24274            self.write(", ");
24275            self.generate_expression(expression)?;
24276        }
24277        self.write(")");
24278        Ok(())
24279    }
24280
24281    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24282        // APPROX_TOP_K_COMBINE(this[, expression])
24283        self.write_keyword("APPROX_TOP_K_COMBINE");
24284        self.write("(");
24285        self.generate_expression(&e.this)?;
24286        if let Some(expression) = &e.expression {
24287            self.write(", ");
24288            self.generate_expression(expression)?;
24289        }
24290        self.write(")");
24291        Ok(())
24292    }
24293
24294    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24295        // APPROX_TOP_K_ESTIMATE(this[, expression])
24296        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24297        self.write("(");
24298        self.generate_expression(&e.this)?;
24299        if let Some(expression) = &e.expression {
24300            self.write(", ");
24301            self.generate_expression(expression)?;
24302        }
24303        self.write(")");
24304        Ok(())
24305    }
24306
24307    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24308        // APPROX_TOP_SUM(this, expression[, count])
24309        self.write_keyword("APPROX_TOP_SUM");
24310        self.write("(");
24311        self.generate_expression(&e.this)?;
24312        self.write(", ");
24313        self.generate_expression(&e.expression)?;
24314        if let Some(count) = &e.count {
24315            self.write(", ");
24316            self.generate_expression(count)?;
24317        }
24318        self.write(")");
24319        Ok(())
24320    }
24321
24322    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24323        // ARG_MAX(this, expression[, count])
24324        self.write_keyword("ARG_MAX");
24325        self.write("(");
24326        self.generate_expression(&e.this)?;
24327        self.write(", ");
24328        self.generate_expression(&e.expression)?;
24329        if let Some(count) = &e.count {
24330            self.write(", ");
24331            self.generate_expression(count)?;
24332        }
24333        self.write(")");
24334        Ok(())
24335    }
24336
24337    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24338        // ARG_MIN(this, expression[, count])
24339        self.write_keyword("ARG_MIN");
24340        self.write("(");
24341        self.generate_expression(&e.this)?;
24342        self.write(", ");
24343        self.generate_expression(&e.expression)?;
24344        if let Some(count) = &e.count {
24345            self.write(", ");
24346            self.generate_expression(count)?;
24347        }
24348        self.write(")");
24349        Ok(())
24350    }
24351
24352    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24353        // ARRAY_ALL(this, expression)
24354        self.write_keyword("ARRAY_ALL");
24355        self.write("(");
24356        self.generate_expression(&e.this)?;
24357        self.write(", ");
24358        self.generate_expression(&e.expression)?;
24359        self.write(")");
24360        Ok(())
24361    }
24362
24363    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24364        // ARRAY_ANY(this, expression) - fallback implementation
24365        self.write_keyword("ARRAY_ANY");
24366        self.write("(");
24367        self.generate_expression(&e.this)?;
24368        self.write(", ");
24369        self.generate_expression(&e.expression)?;
24370        self.write(")");
24371        Ok(())
24372    }
24373
24374    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24375        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24376        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24377        self.write("(");
24378        for (i, expr) in e.expressions.iter().enumerate() {
24379            if i > 0 {
24380                self.write(", ");
24381            }
24382            self.generate_expression(expr)?;
24383        }
24384        self.write(")");
24385        Ok(())
24386    }
24387
24388    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24389        // ARRAY_SUM(this[, expression])
24390        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24391            self.write("arraySum");
24392        } else {
24393            self.write_keyword("ARRAY_SUM");
24394        }
24395        self.write("(");
24396        self.generate_expression(&e.this)?;
24397        if let Some(expression) = &e.expression {
24398            self.write(", ");
24399            self.generate_expression(expression)?;
24400        }
24401        self.write(")");
24402        Ok(())
24403    }
24404
24405    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24406        // Python: return f"{this} AT {index}"
24407        self.generate_expression(&e.this)?;
24408        self.write_space();
24409        self.write_keyword("AT");
24410        self.write_space();
24411        self.generate_expression(&e.expression)?;
24412        Ok(())
24413    }
24414
24415    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24416        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24417        self.write_keyword("ATTACH");
24418        if e.exists {
24419            self.write_space();
24420            self.write_keyword("IF NOT EXISTS");
24421        }
24422        self.write_space();
24423        self.generate_expression(&e.this)?;
24424        if !e.expressions.is_empty() {
24425            self.write(" (");
24426            for (i, expr) in e.expressions.iter().enumerate() {
24427                if i > 0 {
24428                    self.write(", ");
24429                }
24430                self.generate_expression(expr)?;
24431            }
24432            self.write(")");
24433        }
24434        Ok(())
24435    }
24436
24437    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24438        // AttachOption: this [expression]
24439        // Python sqlglot: no equals sign, just space-separated
24440        self.generate_expression(&e.this)?;
24441        if let Some(expression) = &e.expression {
24442            self.write_space();
24443            self.generate_expression(expression)?;
24444        }
24445        Ok(())
24446    }
24447
24448    /// Generate the auto_increment keyword and options for a column definition.
24449    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24450    /// GENERATED AS IDENTITY, etc.
24451    fn generate_auto_increment_keyword(
24452        &mut self,
24453        col: &crate::expressions::ColumnDef,
24454    ) -> Result<()> {
24455        use crate::dialects::DialectType;
24456        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24457            self.write_keyword("IDENTITY");
24458            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24459                self.write("(");
24460                if let Some(ref start) = col.auto_increment_start {
24461                    self.generate_expression(start)?;
24462                } else {
24463                    self.write("0");
24464                }
24465                self.write(", ");
24466                if let Some(ref inc) = col.auto_increment_increment {
24467                    self.generate_expression(inc)?;
24468                } else {
24469                    self.write("1");
24470                }
24471                self.write(")");
24472            }
24473        } else if matches!(
24474            self.config.dialect,
24475            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24476        ) {
24477            self.write_keyword("AUTOINCREMENT");
24478            if let Some(ref start) = col.auto_increment_start {
24479                self.write_space();
24480                self.write_keyword("START");
24481                self.write_space();
24482                self.generate_expression(start)?;
24483            }
24484            if let Some(ref inc) = col.auto_increment_increment {
24485                self.write_space();
24486                self.write_keyword("INCREMENT");
24487                self.write_space();
24488                self.generate_expression(inc)?;
24489            }
24490            if let Some(order) = col.auto_increment_order {
24491                self.write_space();
24492                if order {
24493                    self.write_keyword("ORDER");
24494                } else {
24495                    self.write_keyword("NOORDER");
24496                }
24497            }
24498        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24499            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24500            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24501                self.write(" (");
24502                let mut first = true;
24503                if let Some(ref start) = col.auto_increment_start {
24504                    self.write_keyword("START WITH");
24505                    self.write_space();
24506                    self.generate_expression(start)?;
24507                    first = false;
24508                }
24509                if let Some(ref inc) = col.auto_increment_increment {
24510                    if !first {
24511                        self.write_space();
24512                    }
24513                    self.write_keyword("INCREMENT BY");
24514                    self.write_space();
24515                    self.generate_expression(inc)?;
24516                }
24517                self.write(")");
24518            }
24519        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24520            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24521            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24522                self.write(" (");
24523                let mut first = true;
24524                if let Some(ref start) = col.auto_increment_start {
24525                    self.write_keyword("START WITH");
24526                    self.write_space();
24527                    self.generate_expression(start)?;
24528                    first = false;
24529                }
24530                if let Some(ref inc) = col.auto_increment_increment {
24531                    if !first {
24532                        self.write_space();
24533                    }
24534                    self.write_keyword("INCREMENT BY");
24535                    self.write_space();
24536                    self.generate_expression(inc)?;
24537                }
24538                self.write(")");
24539            }
24540        } else if matches!(
24541            self.config.dialect,
24542            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24543        ) {
24544            self.write_keyword("IDENTITY");
24545            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24546                self.write("(");
24547                if let Some(ref start) = col.auto_increment_start {
24548                    self.generate_expression(start)?;
24549                } else {
24550                    self.write("0");
24551                }
24552                self.write(", ");
24553                if let Some(ref inc) = col.auto_increment_increment {
24554                    self.generate_expression(inc)?;
24555                } else {
24556                    self.write("1");
24557                }
24558                self.write(")");
24559            }
24560        } else {
24561            self.write_keyword("AUTO_INCREMENT");
24562            if let Some(ref start) = col.auto_increment_start {
24563                self.write_space();
24564                self.write_keyword("START");
24565                self.write_space();
24566                self.generate_expression(start)?;
24567            }
24568            if let Some(ref inc) = col.auto_increment_increment {
24569                self.write_space();
24570                self.write_keyword("INCREMENT");
24571                self.write_space();
24572                self.generate_expression(inc)?;
24573            }
24574            if let Some(order) = col.auto_increment_order {
24575                self.write_space();
24576                if order {
24577                    self.write_keyword("ORDER");
24578                } else {
24579                    self.write_keyword("NOORDER");
24580                }
24581            }
24582        }
24583        Ok(())
24584    }
24585
24586    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24587        // AUTO_INCREMENT=value
24588        self.write_keyword("AUTO_INCREMENT");
24589        self.write("=");
24590        self.generate_expression(&e.this)?;
24591        Ok(())
24592    }
24593
24594    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24595        // AUTO_REFRESH=value
24596        self.write_keyword("AUTO_REFRESH");
24597        self.write("=");
24598        self.generate_expression(&e.this)?;
24599        Ok(())
24600    }
24601
24602    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24603        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24604        self.write_keyword("BACKUP");
24605        self.write_space();
24606        self.generate_expression(&e.this)?;
24607        Ok(())
24608    }
24609
24610    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24611        // BASE64_DECODE_BINARY(this[, alphabet])
24612        self.write_keyword("BASE64_DECODE_BINARY");
24613        self.write("(");
24614        self.generate_expression(&e.this)?;
24615        if let Some(alphabet) = &e.alphabet {
24616            self.write(", ");
24617            self.generate_expression(alphabet)?;
24618        }
24619        self.write(")");
24620        Ok(())
24621    }
24622
24623    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24624        // BASE64_DECODE_STRING(this[, alphabet])
24625        self.write_keyword("BASE64_DECODE_STRING");
24626        self.write("(");
24627        self.generate_expression(&e.this)?;
24628        if let Some(alphabet) = &e.alphabet {
24629            self.write(", ");
24630            self.generate_expression(alphabet)?;
24631        }
24632        self.write(")");
24633        Ok(())
24634    }
24635
24636    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24637        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24638        self.write_keyword("BASE64_ENCODE");
24639        self.write("(");
24640        self.generate_expression(&e.this)?;
24641        if let Some(max_line_length) = &e.max_line_length {
24642            self.write(", ");
24643            self.generate_expression(max_line_length)?;
24644        }
24645        if let Some(alphabet) = &e.alphabet {
24646            self.write(", ");
24647            self.generate_expression(alphabet)?;
24648        }
24649        self.write(")");
24650        Ok(())
24651    }
24652
24653    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24654        // BLOCKCOMPRESSION=... (complex Teradata property)
24655        self.write_keyword("BLOCKCOMPRESSION");
24656        self.write("=");
24657        if let Some(autotemp) = &e.autotemp {
24658            self.write_keyword("AUTOTEMP");
24659            self.write("(");
24660            self.generate_expression(autotemp)?;
24661            self.write(")");
24662        }
24663        if let Some(always) = &e.always {
24664            self.generate_expression(always)?;
24665        }
24666        if let Some(default) = &e.default {
24667            self.generate_expression(default)?;
24668        }
24669        if let Some(manual) = &e.manual {
24670            self.generate_expression(manual)?;
24671        }
24672        if let Some(never) = &e.never {
24673            self.generate_expression(never)?;
24674        }
24675        Ok(())
24676    }
24677
24678    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24679        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24680        self.write("((");
24681        self.generate_expression(&e.this)?;
24682        self.write(") ");
24683        self.write_keyword("AND");
24684        self.write(" (");
24685        self.generate_expression(&e.expression)?;
24686        self.write("))");
24687        Ok(())
24688    }
24689
24690    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24691        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24692        self.write("((");
24693        self.generate_expression(&e.this)?;
24694        self.write(") ");
24695        self.write_keyword("OR");
24696        self.write(" (");
24697        self.generate_expression(&e.expression)?;
24698        self.write("))");
24699        Ok(())
24700    }
24701
24702    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24703        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24704        self.write_keyword("BUILD");
24705        self.write_space();
24706        self.generate_expression(&e.this)?;
24707        Ok(())
24708    }
24709
24710    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24711        // Byte string literal like B'...' or X'...'
24712        self.generate_expression(&e.this)?;
24713        Ok(())
24714    }
24715
24716    fn generate_case_specific_column_constraint(
24717        &mut self,
24718        e: &CaseSpecificColumnConstraint,
24719    ) -> Result<()> {
24720        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24721        if e.not_.is_some() {
24722            self.write_keyword("NOT");
24723            self.write_space();
24724        }
24725        self.write_keyword("CASESPECIFIC");
24726        Ok(())
24727    }
24728
24729    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24730        // Cast to string type (dialect-specific)
24731        self.write_keyword("CAST");
24732        self.write("(");
24733        self.generate_expression(&e.this)?;
24734        if self.config.dialect == Some(DialectType::ClickHouse) {
24735            // ClickHouse: CAST(expr, 'type_string')
24736            self.write(", ");
24737        } else {
24738            self.write_space();
24739            self.write_keyword("AS");
24740            self.write_space();
24741        }
24742        if let Some(to) = &e.to {
24743            self.generate_expression(to)?;
24744        }
24745        self.write(")");
24746        Ok(())
24747    }
24748
24749    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24750        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24751        // Python: f"CHANGES ({information}){at_before}{end}"
24752        self.write_keyword("CHANGES");
24753        self.write(" (");
24754        if let Some(information) = &e.information {
24755            self.write_keyword("INFORMATION");
24756            self.write(" => ");
24757            self.generate_expression(information)?;
24758        }
24759        self.write(")");
24760        // at_before and end are HistoricalData expressions that generate their own keywords
24761        if let Some(at_before) = &e.at_before {
24762            self.write(" ");
24763            self.generate_expression(at_before)?;
24764        }
24765        if let Some(end) = &e.end {
24766            self.write(" ");
24767            self.generate_expression(end)?;
24768        }
24769        Ok(())
24770    }
24771
24772    fn generate_character_set_column_constraint(
24773        &mut self,
24774        e: &CharacterSetColumnConstraint,
24775    ) -> Result<()> {
24776        // CHARACTER SET charset_name
24777        self.write_keyword("CHARACTER SET");
24778        self.write_space();
24779        self.generate_expression(&e.this)?;
24780        Ok(())
24781    }
24782
24783    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24784        // [DEFAULT] CHARACTER SET=value
24785        if e.default.is_some() {
24786            self.write_keyword("DEFAULT");
24787            self.write_space();
24788        }
24789        self.write_keyword("CHARACTER SET");
24790        self.write("=");
24791        self.generate_expression(&e.this)?;
24792        Ok(())
24793    }
24794
24795    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24796        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24797        self.write_keyword("CHECK");
24798        self.write(" (");
24799        self.generate_expression(&e.this)?;
24800        self.write(")");
24801        if e.enforced.is_some() {
24802            self.write_space();
24803            self.write_keyword("ENFORCED");
24804        }
24805        Ok(())
24806    }
24807
24808    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24809        // CHECK_JSON(this)
24810        self.write_keyword("CHECK_JSON");
24811        self.write("(");
24812        self.generate_expression(&e.this)?;
24813        self.write(")");
24814        Ok(())
24815    }
24816
24817    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24818        // CHECK_XML(this)
24819        self.write_keyword("CHECK_XML");
24820        self.write("(");
24821        self.generate_expression(&e.this)?;
24822        self.write(")");
24823        Ok(())
24824    }
24825
24826    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24827        // CHECKSUM=[ON|OFF|DEFAULT]
24828        self.write_keyword("CHECKSUM");
24829        self.write("=");
24830        if e.on.is_some() {
24831            self.write_keyword("ON");
24832        } else if e.default.is_some() {
24833            self.write_keyword("DEFAULT");
24834        } else {
24835            self.write_keyword("OFF");
24836        }
24837        Ok(())
24838    }
24839
24840    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24841        // Python: return f"{shallow}{keyword} {this}"
24842        if e.shallow.is_some() {
24843            self.write_keyword("SHALLOW");
24844            self.write_space();
24845        }
24846        if e.copy.is_some() {
24847            self.write_keyword("COPY");
24848        } else {
24849            self.write_keyword("CLONE");
24850        }
24851        self.write_space();
24852        self.generate_expression(&e.this)?;
24853        Ok(())
24854    }
24855
24856    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24857        // CLUSTER BY (expressions)
24858        self.write_keyword("CLUSTER BY");
24859        self.write(" (");
24860        for (i, ord) in e.expressions.iter().enumerate() {
24861            if i > 0 {
24862                self.write(", ");
24863            }
24864            self.generate_ordered(ord)?;
24865        }
24866        self.write(")");
24867        Ok(())
24868    }
24869
24870    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
24871        // BigQuery table property: CLUSTER BY col1, col2
24872        self.write_keyword("CLUSTER BY");
24873        self.write_space();
24874        for (i, col) in e.columns.iter().enumerate() {
24875            if i > 0 {
24876                self.write(", ");
24877            }
24878            self.generate_identifier(col)?;
24879        }
24880        Ok(())
24881    }
24882
24883    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24884        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24885        self.write_keyword("CLUSTERED BY");
24886        self.write(" (");
24887        for (i, expr) in e.expressions.iter().enumerate() {
24888            if i > 0 {
24889                self.write(", ");
24890            }
24891            self.generate_expression(expr)?;
24892        }
24893        self.write(")");
24894        if let Some(sorted_by) = &e.sorted_by {
24895            self.write_space();
24896            self.write_keyword("SORTED BY");
24897            self.write(" (");
24898            // Unwrap Tuple to avoid double parentheses
24899            if let Expression::Tuple(t) = sorted_by.as_ref() {
24900                for (i, expr) in t.expressions.iter().enumerate() {
24901                    if i > 0 {
24902                        self.write(", ");
24903                    }
24904                    self.generate_expression(expr)?;
24905                }
24906            } else {
24907                self.generate_expression(sorted_by)?;
24908            }
24909            self.write(")");
24910        }
24911        if let Some(buckets) = &e.buckets {
24912            self.write_space();
24913            self.write_keyword("INTO");
24914            self.write_space();
24915            self.generate_expression(buckets)?;
24916            self.write_space();
24917            self.write_keyword("BUCKETS");
24918        }
24919        Ok(())
24920    }
24921
24922    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24923        // [DEFAULT] COLLATE [=] value
24924        // BigQuery uses space: DEFAULT COLLATE 'en'
24925        // Others use equals: COLLATE='en'
24926        if e.default.is_some() {
24927            self.write_keyword("DEFAULT");
24928            self.write_space();
24929        }
24930        self.write_keyword("COLLATE");
24931        // BigQuery uses space between COLLATE and value
24932        match self.config.dialect {
24933            Some(DialectType::BigQuery) => self.write_space(),
24934            _ => self.write("="),
24935        }
24936        self.generate_expression(&e.this)?;
24937        Ok(())
24938    }
24939
24940    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24941        // ColumnConstraint is an enum
24942        match e {
24943            ColumnConstraint::NotNull => {
24944                self.write_keyword("NOT NULL");
24945            }
24946            ColumnConstraint::Null => {
24947                self.write_keyword("NULL");
24948            }
24949            ColumnConstraint::Unique => {
24950                self.write_keyword("UNIQUE");
24951            }
24952            ColumnConstraint::PrimaryKey => {
24953                self.write_keyword("PRIMARY KEY");
24954            }
24955            ColumnConstraint::Default(expr) => {
24956                self.write_keyword("DEFAULT");
24957                self.write_space();
24958                self.generate_expression(expr)?;
24959            }
24960            ColumnConstraint::Check(expr) => {
24961                self.write_keyword("CHECK");
24962                self.write(" (");
24963                self.generate_expression(expr)?;
24964                self.write(")");
24965            }
24966            ColumnConstraint::References(fk_ref) => {
24967                if fk_ref.has_foreign_key_keywords {
24968                    self.write_keyword("FOREIGN KEY");
24969                    self.write_space();
24970                }
24971                self.write_keyword("REFERENCES");
24972                self.write_space();
24973                self.generate_table(&fk_ref.table)?;
24974                if !fk_ref.columns.is_empty() {
24975                    self.write(" (");
24976                    for (i, col) in fk_ref.columns.iter().enumerate() {
24977                        if i > 0 {
24978                            self.write(", ");
24979                        }
24980                        self.generate_identifier(col)?;
24981                    }
24982                    self.write(")");
24983                }
24984            }
24985            ColumnConstraint::GeneratedAsIdentity(gen) => {
24986                self.write_keyword("GENERATED");
24987                self.write_space();
24988                if gen.always {
24989                    self.write_keyword("ALWAYS");
24990                } else {
24991                    self.write_keyword("BY DEFAULT");
24992                    if gen.on_null {
24993                        self.write_space();
24994                        self.write_keyword("ON NULL");
24995                    }
24996                }
24997                self.write_space();
24998                self.write_keyword("AS IDENTITY");
24999            }
25000            ColumnConstraint::Collate(collation) => {
25001                self.write_keyword("COLLATE");
25002                self.write_space();
25003                self.generate_identifier(collation)?;
25004            }
25005            ColumnConstraint::Comment(comment) => {
25006                self.write_keyword("COMMENT");
25007                self.write(" '");
25008                self.write(comment);
25009                self.write("'");
25010            }
25011            ColumnConstraint::ComputedColumn(cc) => {
25012                self.generate_computed_column_inline(cc)?;
25013            }
25014            ColumnConstraint::GeneratedAsRow(gar) => {
25015                self.generate_generated_as_row_inline(gar)?;
25016            }
25017            ColumnConstraint::Tags(tags) => {
25018                self.write_keyword("TAG");
25019                self.write(" (");
25020                for (i, expr) in tags.expressions.iter().enumerate() {
25021                    if i > 0 {
25022                        self.write(", ");
25023                    }
25024                    self.generate_expression(expr)?;
25025                }
25026                self.write(")");
25027            }
25028            ColumnConstraint::Path(path_expr) => {
25029                self.write_keyword("PATH");
25030                self.write_space();
25031                self.generate_expression(path_expr)?;
25032            }
25033        }
25034        Ok(())
25035    }
25036
25037    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
25038        // ColumnPosition is an enum
25039        match e {
25040            ColumnPosition::First => {
25041                self.write_keyword("FIRST");
25042            }
25043            ColumnPosition::After(ident) => {
25044                self.write_keyword("AFTER");
25045                self.write_space();
25046                self.generate_identifier(ident)?;
25047            }
25048        }
25049        Ok(())
25050    }
25051
25052    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
25053        // column(prefix)
25054        self.generate_expression(&e.this)?;
25055        self.write("(");
25056        self.generate_expression(&e.expression)?;
25057        self.write(")");
25058        Ok(())
25059    }
25060
25061    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
25062        // If unpack is true, this came from * COLUMNS(pattern)
25063        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
25064        if let Some(ref unpack) = e.unpack {
25065            if let Expression::Boolean(b) = unpack.as_ref() {
25066                if b.value {
25067                    self.write("*");
25068                }
25069            }
25070        }
25071        self.write_keyword("COLUMNS");
25072        self.write("(");
25073        self.generate_expression(&e.this)?;
25074        self.write(")");
25075        Ok(())
25076    }
25077
25078    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25079        // Combined aggregate: FUNC(args) combined
25080        self.generate_expression(&e.this)?;
25081        self.write("(");
25082        for (i, expr) in e.expressions.iter().enumerate() {
25083            if i > 0 {
25084                self.write(", ");
25085            }
25086            self.generate_expression(expr)?;
25087        }
25088        self.write(")");
25089        Ok(())
25090    }
25091
25092    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25093        // Combined parameterized aggregate: FUNC(params)(expressions)
25094        self.generate_expression(&e.this)?;
25095        self.write("(");
25096        for (i, param) in e.params.iter().enumerate() {
25097            if i > 0 {
25098                self.write(", ");
25099            }
25100            self.generate_expression(param)?;
25101        }
25102        self.write(")(");
25103        for (i, expr) in e.expressions.iter().enumerate() {
25104            if i > 0 {
25105                self.write(", ");
25106            }
25107            self.generate_expression(expr)?;
25108        }
25109        self.write(")");
25110        Ok(())
25111    }
25112
25113    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25114        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25115        self.write_keyword("COMMIT");
25116
25117        // TSQL always uses COMMIT TRANSACTION
25118        if e.this.is_none()
25119            && matches!(
25120                self.config.dialect,
25121                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25122            )
25123        {
25124            self.write_space();
25125            self.write_keyword("TRANSACTION");
25126        }
25127
25128        // Check if this has TRANSACTION keyword or transaction name
25129        if let Some(this) = &e.this {
25130            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25131            let is_transaction_marker = matches!(
25132                this.as_ref(),
25133                Expression::Identifier(id) if id.name == "TRANSACTION"
25134            );
25135
25136            self.write_space();
25137            self.write_keyword("TRANSACTION");
25138
25139            // If it's a real transaction name, output it
25140            if !is_transaction_marker {
25141                self.write_space();
25142                self.generate_expression(this)?;
25143            }
25144        }
25145
25146        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25147        if let Some(durability) = &e.durability {
25148            self.write_space();
25149            self.write_keyword("WITH");
25150            self.write(" (");
25151            self.write_keyword("DELAYED_DURABILITY");
25152            self.write(" = ");
25153            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25154                self.write_keyword("ON");
25155            } else {
25156                self.write_keyword("OFF");
25157            }
25158            self.write(")");
25159        }
25160
25161        // Output AND [NO] CHAIN
25162        if let Some(chain) = &e.chain {
25163            self.write_space();
25164            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25165                self.write_keyword("AND NO CHAIN");
25166            } else {
25167                self.write_keyword("AND CHAIN");
25168            }
25169        }
25170        Ok(())
25171    }
25172
25173    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25174        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25175        self.write("[");
25176        self.generate_expression(&e.this)?;
25177        self.write_space();
25178        self.write_keyword("FOR");
25179        self.write_space();
25180        self.generate_expression(&e.expression)?;
25181        // Handle optional position variable (for enumerate-like syntax)
25182        if let Some(pos) = &e.position {
25183            self.write(", ");
25184            self.generate_expression(pos)?;
25185        }
25186        if let Some(iterator) = &e.iterator {
25187            self.write_space();
25188            self.write_keyword("IN");
25189            self.write_space();
25190            self.generate_expression(iterator)?;
25191        }
25192        if let Some(condition) = &e.condition {
25193            self.write_space();
25194            self.write_keyword("IF");
25195            self.write_space();
25196            self.generate_expression(condition)?;
25197        }
25198        self.write("]");
25199        Ok(())
25200    }
25201
25202    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25203        // COMPRESS(this[, method])
25204        self.write_keyword("COMPRESS");
25205        self.write("(");
25206        self.generate_expression(&e.this)?;
25207        if let Some(method) = &e.method {
25208            self.write(", '");
25209            self.write(method);
25210            self.write("'");
25211        }
25212        self.write(")");
25213        Ok(())
25214    }
25215
25216    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25217        // Python: return f"COMPRESS {this}"
25218        self.write_keyword("COMPRESS");
25219        if let Some(this) = &e.this {
25220            self.write_space();
25221            self.generate_expression(this)?;
25222        }
25223        Ok(())
25224    }
25225
25226    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25227        // Python: return f"AS {this}{persisted}"
25228        self.write_keyword("AS");
25229        self.write_space();
25230        self.generate_expression(&e.this)?;
25231        if e.not_null.is_some() {
25232            self.write_space();
25233            self.write_keyword("PERSISTED NOT NULL");
25234        } else if e.persisted.is_some() {
25235            self.write_space();
25236            self.write_keyword("PERSISTED");
25237        }
25238        Ok(())
25239    }
25240
25241    /// Generate a ComputedColumn constraint inline within a column definition.
25242    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25243    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25244    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25245        let computed_expr = if matches!(
25246            self.config.dialect,
25247            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25248        ) {
25249            match &*cc.expression {
25250                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25251                {
25252                    let wrapped = Expression::Cast(Box::new(Cast {
25253                        this: y.this.clone(),
25254                        to: DataType::Date,
25255                        trailing_comments: Vec::new(),
25256                        double_colon_syntax: false,
25257                        format: None,
25258                        default: None,
25259                        inferred_type: None,
25260                    }));
25261                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25262                }
25263                Expression::Function(f)
25264                    if f.name.eq_ignore_ascii_case("YEAR")
25265                        && f.args.len() == 1
25266                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25267                {
25268                    let wrapped = Expression::Cast(Box::new(Cast {
25269                        this: f.args[0].clone(),
25270                        to: DataType::Date,
25271                        trailing_comments: Vec::new(),
25272                        double_colon_syntax: false,
25273                        format: None,
25274                        default: None,
25275                        inferred_type: None,
25276                    }));
25277                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25278                }
25279                _ => *cc.expression.clone(),
25280            }
25281        } else {
25282            *cc.expression.clone()
25283        };
25284
25285        match cc.persistence_kind.as_deref() {
25286            Some("STORED") | Some("VIRTUAL") => {
25287                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25288                self.write_keyword("GENERATED ALWAYS AS");
25289                self.write(" (");
25290                self.generate_expression(&computed_expr)?;
25291                self.write(")");
25292                self.write_space();
25293                if cc.persisted {
25294                    self.write_keyword("STORED");
25295                } else {
25296                    self.write_keyword("VIRTUAL");
25297                }
25298            }
25299            Some("PERSISTED") => {
25300                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25301                self.write_keyword("AS");
25302                self.write(" (");
25303                self.generate_expression(&computed_expr)?;
25304                self.write(")");
25305                self.write_space();
25306                self.write_keyword("PERSISTED");
25307                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25308                if let Some(ref dt) = cc.data_type {
25309                    self.write_space();
25310                    self.generate_data_type(dt)?;
25311                }
25312                if cc.not_null {
25313                    self.write_space();
25314                    self.write_keyword("NOT NULL");
25315                }
25316            }
25317            _ => {
25318                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25319                // TSQL computed column without PERSISTED: AS (expr)
25320                if matches!(
25321                    self.config.dialect,
25322                    Some(DialectType::Spark)
25323                        | Some(DialectType::Databricks)
25324                        | Some(DialectType::Hive)
25325                ) {
25326                    self.write_keyword("GENERATED ALWAYS AS");
25327                    self.write(" (");
25328                    self.generate_expression(&computed_expr)?;
25329                    self.write(")");
25330                } else if matches!(
25331                    self.config.dialect,
25332                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25333                ) {
25334                    self.write_keyword("AS");
25335                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25336                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25337                    if omit_parens {
25338                        self.write_space();
25339                        self.generate_expression(&computed_expr)?;
25340                    } else {
25341                        self.write(" (");
25342                        self.generate_expression(&computed_expr)?;
25343                        self.write(")");
25344                    }
25345                } else {
25346                    self.write_keyword("AS");
25347                    self.write(" (");
25348                    self.generate_expression(&computed_expr)?;
25349                    self.write(")");
25350                }
25351            }
25352        }
25353        Ok(())
25354    }
25355
25356    /// Generate a GeneratedAsRow constraint inline within a column definition.
25357    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25358    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25359        self.write_keyword("GENERATED ALWAYS AS ROW ");
25360        if gar.start {
25361            self.write_keyword("START");
25362        } else {
25363            self.write_keyword("END");
25364        }
25365        if gar.hidden {
25366            self.write_space();
25367            self.write_keyword("HIDDEN");
25368        }
25369        Ok(())
25370    }
25371
25372    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25373    fn generate_system_versioning_content(
25374        &mut self,
25375        e: &WithSystemVersioningProperty,
25376    ) -> Result<()> {
25377        let mut parts = Vec::new();
25378
25379        if let Some(this) = &e.this {
25380            let mut s = String::from("HISTORY_TABLE=");
25381            let mut gen = Generator::new();
25382            gen.config = self.config.clone();
25383            gen.generate_expression(this)?;
25384            s.push_str(&gen.output);
25385            parts.push(s);
25386        }
25387
25388        if let Some(data_consistency) = &e.data_consistency {
25389            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25390            let mut gen = Generator::new();
25391            gen.config = self.config.clone();
25392            gen.generate_expression(data_consistency)?;
25393            s.push_str(&gen.output);
25394            parts.push(s);
25395        }
25396
25397        if let Some(retention_period) = &e.retention_period {
25398            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25399            let mut gen = Generator::new();
25400            gen.config = self.config.clone();
25401            gen.generate_expression(retention_period)?;
25402            s.push_str(&gen.output);
25403            parts.push(s);
25404        }
25405
25406        self.write_keyword("SYSTEM_VERSIONING");
25407        self.write("=");
25408
25409        if !parts.is_empty() {
25410            self.write_keyword("ON");
25411            self.write("(");
25412            self.write(&parts.join(", "));
25413            self.write(")");
25414        } else if e.on.is_some() {
25415            self.write_keyword("ON");
25416        } else {
25417            self.write_keyword("OFF");
25418        }
25419
25420        Ok(())
25421    }
25422
25423    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25424        // Conditional INSERT for multi-table inserts
25425        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25426        if e.else_.is_some() {
25427            self.write_keyword("ELSE");
25428            self.write_space();
25429        } else if let Some(expression) = &e.expression {
25430            self.write_keyword("WHEN");
25431            self.write_space();
25432            self.generate_expression(expression)?;
25433            self.write_space();
25434            self.write_keyword("THEN");
25435            self.write_space();
25436        }
25437
25438        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25439        // without the "INSERT " prefix
25440        if let Expression::Insert(insert) = e.this.as_ref() {
25441            self.write_keyword("INTO");
25442            self.write_space();
25443            self.generate_table(&insert.table)?;
25444
25445            // Optional column list
25446            if !insert.columns.is_empty() {
25447                self.write(" (");
25448                for (i, col) in insert.columns.iter().enumerate() {
25449                    if i > 0 {
25450                        self.write(", ");
25451                    }
25452                    self.generate_identifier(col)?;
25453                }
25454                self.write(")");
25455            }
25456
25457            // Optional VALUES clause
25458            if !insert.values.is_empty() {
25459                self.write_space();
25460                self.write_keyword("VALUES");
25461                for (row_idx, row) in insert.values.iter().enumerate() {
25462                    if row_idx > 0 {
25463                        self.write(", ");
25464                    }
25465                    self.write(" (");
25466                    for (i, val) in row.iter().enumerate() {
25467                        if i > 0 {
25468                            self.write(", ");
25469                        }
25470                        self.generate_expression(val)?;
25471                    }
25472                    self.write(")");
25473                }
25474            }
25475        } else {
25476            // Fallback for non-Insert expressions
25477            self.generate_expression(&e.this)?;
25478        }
25479        Ok(())
25480    }
25481
25482    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25483        // Python: return f"CONSTRAINT {this} {expressions}"
25484        self.write_keyword("CONSTRAINT");
25485        self.write_space();
25486        self.generate_expression(&e.this)?;
25487        if !e.expressions.is_empty() {
25488            self.write_space();
25489            for (i, expr) in e.expressions.iter().enumerate() {
25490                if i > 0 {
25491                    self.write_space();
25492                }
25493                self.generate_expression(expr)?;
25494            }
25495        }
25496        Ok(())
25497    }
25498
25499    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25500        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25501        self.write_keyword("CONVERT_TIMEZONE");
25502        self.write("(");
25503        let mut first = true;
25504        if let Some(source_tz) = &e.source_tz {
25505            self.generate_expression(source_tz)?;
25506            first = false;
25507        }
25508        if let Some(target_tz) = &e.target_tz {
25509            if !first {
25510                self.write(", ");
25511            }
25512            self.generate_expression(target_tz)?;
25513            first = false;
25514        }
25515        if let Some(timestamp) = &e.timestamp {
25516            if !first {
25517                self.write(", ");
25518            }
25519            self.generate_expression(timestamp)?;
25520        }
25521        self.write(")");
25522        Ok(())
25523    }
25524
25525    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25526        // CONVERT(this USING dest)
25527        self.write_keyword("CONVERT");
25528        self.write("(");
25529        self.generate_expression(&e.this)?;
25530        if let Some(dest) = &e.dest {
25531            self.write_space();
25532            self.write_keyword("USING");
25533            self.write_space();
25534            self.generate_expression(dest)?;
25535        }
25536        self.write(")");
25537        Ok(())
25538    }
25539
25540    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25541        self.write_keyword("COPY");
25542        if e.is_into {
25543            self.write_space();
25544            self.write_keyword("INTO");
25545        }
25546        self.write_space();
25547
25548        // Generate target table or query (or stage for COPY INTO @stage)
25549        if let Expression::Literal(Literal::String(s)) = &e.this {
25550            if s.starts_with('@') {
25551                self.write(s);
25552            } else {
25553                self.generate_expression(&e.this)?;
25554            }
25555        } else {
25556            self.generate_expression(&e.this)?;
25557        }
25558
25559        // FROM or TO based on kind
25560        if e.kind {
25561            // kind=true means FROM (loading into table)
25562            if self.config.pretty {
25563                self.write_newline();
25564            } else {
25565                self.write_space();
25566            }
25567            self.write_keyword("FROM");
25568            self.write_space();
25569        } else if !e.files.is_empty() {
25570            // kind=false means TO (exporting)
25571            if self.config.pretty {
25572                self.write_newline();
25573            } else {
25574                self.write_space();
25575            }
25576            self.write_keyword("TO");
25577            self.write_space();
25578        }
25579
25580        // Generate source/destination files
25581        for (i, file) in e.files.iter().enumerate() {
25582            if i > 0 {
25583                self.write_space();
25584            }
25585            // For stage references (strings starting with @), output without quotes
25586            if let Expression::Literal(Literal::String(s)) = file {
25587                if s.starts_with('@') {
25588                    self.write(s);
25589                } else {
25590                    self.generate_expression(file)?;
25591                }
25592            } else if let Expression::Identifier(id) = file {
25593                // Backtick-quoted file path (Databricks style: `s3://link`)
25594                if id.quoted {
25595                    self.write("`");
25596                    self.write(&id.name);
25597                    self.write("`");
25598                } else {
25599                    self.generate_expression(file)?;
25600                }
25601            } else {
25602                self.generate_expression(file)?;
25603            }
25604        }
25605
25606        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25607        if !e.with_wrapped {
25608            if let Some(ref creds) = e.credentials {
25609                if let Some(ref storage) = creds.storage {
25610                    if self.config.pretty {
25611                        self.write_newline();
25612                    } else {
25613                        self.write_space();
25614                    }
25615                    self.write_keyword("STORAGE_INTEGRATION");
25616                    self.write(" = ");
25617                    self.write(storage);
25618                }
25619                if creds.credentials.is_empty() {
25620                    // Empty credentials: CREDENTIALS = ()
25621                    if self.config.pretty {
25622                        self.write_newline();
25623                    } else {
25624                        self.write_space();
25625                    }
25626                    self.write_keyword("CREDENTIALS");
25627                    self.write(" = ()");
25628                } else {
25629                    if self.config.pretty {
25630                        self.write_newline();
25631                    } else {
25632                        self.write_space();
25633                    }
25634                    self.write_keyword("CREDENTIALS");
25635                    // Check if this is Redshift-style (single value with empty key)
25636                    // vs Snowflake-style (multiple key=value pairs)
25637                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25638                        // Redshift style: CREDENTIALS 'value'
25639                        self.write(" '");
25640                        self.write(&creds.credentials[0].1);
25641                        self.write("'");
25642                    } else {
25643                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25644                        self.write(" = (");
25645                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25646                            if i > 0 {
25647                                self.write_space();
25648                            }
25649                            self.write(k);
25650                            self.write("='");
25651                            self.write(v);
25652                            self.write("'");
25653                        }
25654                        self.write(")");
25655                    }
25656                }
25657                if let Some(ref encryption) = creds.encryption {
25658                    self.write_space();
25659                    self.write_keyword("ENCRYPTION");
25660                    self.write(" = ");
25661                    self.write(encryption);
25662                }
25663            }
25664        }
25665
25666        // Generate parameters
25667        if !e.params.is_empty() {
25668            if e.with_wrapped {
25669                // DuckDB/PostgreSQL/TSQL WITH (...) format
25670                self.write_space();
25671                self.write_keyword("WITH");
25672                self.write(" (");
25673                for (i, param) in e.params.iter().enumerate() {
25674                    if i > 0 {
25675                        self.write(", ");
25676                    }
25677                    self.generate_copy_param_with_format(param)?;
25678                }
25679                self.write(")");
25680            } else {
25681                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25682                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25683                // For Snowflake: KEY = VALUE
25684                for param in &e.params {
25685                    if self.config.pretty {
25686                        self.write_newline();
25687                    } else {
25688                        self.write_space();
25689                    }
25690                    // Preserve original case of parameter name (important for Redshift COPY options)
25691                    self.write(&param.name);
25692                    if let Some(ref value) = param.value {
25693                        // Use = only if it was present in the original (param.eq)
25694                        if param.eq {
25695                            self.write(" = ");
25696                        } else {
25697                            self.write(" ");
25698                        }
25699                        if !param.values.is_empty() {
25700                            self.write("(");
25701                            for (i, v) in param.values.iter().enumerate() {
25702                                if i > 0 {
25703                                    self.write_space();
25704                                }
25705                                self.generate_copy_nested_param(v)?;
25706                            }
25707                            self.write(")");
25708                        } else {
25709                            // For COPY parameter values, output identifiers without quoting
25710                            self.generate_copy_param_value(value)?;
25711                        }
25712                    } else if !param.values.is_empty() {
25713                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25714                        if param.eq {
25715                            self.write(" = (");
25716                        } else {
25717                            self.write(" (");
25718                        }
25719                        // Determine separator for values inside parentheses:
25720                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25721                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25722                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25723                        let is_key_value_pairs = param
25724                            .values
25725                            .first()
25726                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25727                        let sep = if is_key_value_pairs && param.eq {
25728                            " "
25729                        } else {
25730                            ", "
25731                        };
25732                        for (i, v) in param.values.iter().enumerate() {
25733                            if i > 0 {
25734                                self.write(sep);
25735                            }
25736                            self.generate_copy_nested_param(v)?;
25737                        }
25738                        self.write(")");
25739                    }
25740                }
25741            }
25742        }
25743
25744        Ok(())
25745    }
25746
25747    /// Generate a COPY parameter in WITH (...) format
25748    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25749    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25750        self.write_keyword(&param.name);
25751        if !param.values.is_empty() {
25752            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25753            self.write(" = (");
25754            for (i, v) in param.values.iter().enumerate() {
25755                if i > 0 {
25756                    self.write(", ");
25757                }
25758                self.generate_copy_nested_param(v)?;
25759            }
25760            self.write(")");
25761        } else if let Some(ref value) = param.value {
25762            if param.eq {
25763                self.write(" = ");
25764            } else {
25765                self.write(" ");
25766            }
25767            self.generate_expression(value)?;
25768        }
25769        Ok(())
25770    }
25771
25772    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25773    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25774        match expr {
25775            Expression::Eq(eq) => {
25776                // Generate key
25777                match &eq.left {
25778                    Expression::Column(c) => self.write(&c.name.name),
25779                    _ => self.generate_expression(&eq.left)?,
25780                }
25781                self.write("=");
25782                // Generate value
25783                match &eq.right {
25784                    Expression::Literal(Literal::String(s)) => {
25785                        self.write("'");
25786                        self.write(s);
25787                        self.write("'");
25788                    }
25789                    Expression::Tuple(t) => {
25790                        // For lists like NULL_IF=('', 'str1')
25791                        self.write("(");
25792                        if self.config.pretty {
25793                            self.write_newline();
25794                            self.indent_level += 1;
25795                            for (i, item) in t.expressions.iter().enumerate() {
25796                                if i > 0 {
25797                                    self.write(", ");
25798                                }
25799                                self.write_indent();
25800                                self.generate_expression(item)?;
25801                            }
25802                            self.write_newline();
25803                            self.indent_level -= 1;
25804                        } else {
25805                            for (i, item) in t.expressions.iter().enumerate() {
25806                                if i > 0 {
25807                                    self.write(", ");
25808                                }
25809                                self.generate_expression(item)?;
25810                            }
25811                        }
25812                        self.write(")");
25813                    }
25814                    _ => self.generate_expression(&eq.right)?,
25815                }
25816                Ok(())
25817            }
25818            Expression::Column(c) => {
25819                // Standalone keyword like COMPRESSION
25820                self.write(&c.name.name);
25821                Ok(())
25822            }
25823            _ => self.generate_expression(expr),
25824        }
25825    }
25826
25827    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25828    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25829    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25830        match expr {
25831            Expression::Column(c) => {
25832                // Output identifier, preserving quotes if originally quoted
25833                if c.name.quoted {
25834                    self.write("\"");
25835                    self.write(&c.name.name);
25836                    self.write("\"");
25837                } else {
25838                    self.write(&c.name.name);
25839                }
25840                Ok(())
25841            }
25842            Expression::Identifier(id) => {
25843                // Output identifier, preserving quotes if originally quoted
25844                if id.quoted {
25845                    self.write("\"");
25846                    self.write(&id.name);
25847                    self.write("\"");
25848                } else {
25849                    self.write(&id.name);
25850                }
25851                Ok(())
25852            }
25853            Expression::Literal(Literal::String(s)) => {
25854                // Output string with quotes
25855                self.write("'");
25856                self.write(s);
25857                self.write("'");
25858                Ok(())
25859            }
25860            _ => self.generate_expression(expr),
25861        }
25862    }
25863
25864    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25865        self.write_keyword(&e.name);
25866        if let Some(ref value) = e.value {
25867            if e.eq {
25868                self.write(" = ");
25869            } else {
25870                self.write(" ");
25871            }
25872            self.generate_expression(value)?;
25873        }
25874        if !e.values.is_empty() {
25875            if e.eq {
25876                self.write(" = ");
25877            } else {
25878                self.write(" ");
25879            }
25880            self.write("(");
25881            for (i, v) in e.values.iter().enumerate() {
25882                if i > 0 {
25883                    self.write(", ");
25884                }
25885                self.generate_expression(v)?;
25886            }
25887            self.write(")");
25888        }
25889        Ok(())
25890    }
25891
25892    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25893        // CORR(this, expression)
25894        self.write_keyword("CORR");
25895        self.write("(");
25896        self.generate_expression(&e.this)?;
25897        self.write(", ");
25898        self.generate_expression(&e.expression)?;
25899        self.write(")");
25900        Ok(())
25901    }
25902
25903    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25904        // COSINE_DISTANCE(this, expression)
25905        self.write_keyword("COSINE_DISTANCE");
25906        self.write("(");
25907        self.generate_expression(&e.this)?;
25908        self.write(", ");
25909        self.generate_expression(&e.expression)?;
25910        self.write(")");
25911        Ok(())
25912    }
25913
25914    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25915        // COVAR_POP(this, expression)
25916        self.write_keyword("COVAR_POP");
25917        self.write("(");
25918        self.generate_expression(&e.this)?;
25919        self.write(", ");
25920        self.generate_expression(&e.expression)?;
25921        self.write(")");
25922        Ok(())
25923    }
25924
25925    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25926        // COVAR_SAMP(this, expression)
25927        self.write_keyword("COVAR_SAMP");
25928        self.write("(");
25929        self.generate_expression(&e.this)?;
25930        self.write(", ");
25931        self.generate_expression(&e.expression)?;
25932        self.write(")");
25933        Ok(())
25934    }
25935
25936    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25937        // CREDENTIALS (key1='value1', key2='value2')
25938        self.write_keyword("CREDENTIALS");
25939        self.write(" (");
25940        for (i, (key, value)) in e.credentials.iter().enumerate() {
25941            if i > 0 {
25942                self.write(", ");
25943            }
25944            self.write(key);
25945            self.write("='");
25946            self.write(value);
25947            self.write("'");
25948        }
25949        self.write(")");
25950        Ok(())
25951    }
25952
25953    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25954        // CREDENTIALS=(expressions)
25955        self.write_keyword("CREDENTIALS");
25956        self.write("=(");
25957        for (i, expr) in e.expressions.iter().enumerate() {
25958            if i > 0 {
25959                self.write(", ");
25960            }
25961            self.generate_expression(expr)?;
25962        }
25963        self.write(")");
25964        Ok(())
25965    }
25966
25967    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25968        use crate::dialects::DialectType;
25969
25970        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25971        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25972        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25973            self.generate_expression(&e.this)?;
25974            self.write_space();
25975            self.write_keyword("AS");
25976            self.write_space();
25977            self.generate_identifier(&e.alias)?;
25978            return Ok(());
25979        }
25980        self.write(&e.alias.name);
25981
25982        // BigQuery doesn't support column aliases in CTE definitions
25983        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25984
25985        if !e.columns.is_empty() && !skip_cte_columns {
25986            self.write("(");
25987            for (i, col) in e.columns.iter().enumerate() {
25988                if i > 0 {
25989                    self.write(", ");
25990                }
25991                self.write(&col.name);
25992            }
25993            self.write(")");
25994        }
25995        // USING KEY (columns) for DuckDB recursive CTEs
25996        if !e.key_expressions.is_empty() {
25997            self.write_space();
25998            self.write_keyword("USING KEY");
25999            self.write(" (");
26000            for (i, key) in e.key_expressions.iter().enumerate() {
26001                if i > 0 {
26002                    self.write(", ");
26003                }
26004                self.write(&key.name);
26005            }
26006            self.write(")");
26007        }
26008        self.write_space();
26009        self.write_keyword("AS");
26010        self.write_space();
26011        if let Some(materialized) = e.materialized {
26012            if materialized {
26013                self.write_keyword("MATERIALIZED");
26014            } else {
26015                self.write_keyword("NOT MATERIALIZED");
26016            }
26017            self.write_space();
26018        }
26019        self.write("(");
26020        self.generate_expression(&e.this)?;
26021        self.write(")");
26022        Ok(())
26023    }
26024
26025    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
26026        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
26027        if e.expressions.is_empty() {
26028            self.write_keyword("WITH CUBE");
26029        } else {
26030            self.write_keyword("CUBE");
26031            self.write("(");
26032            for (i, expr) in e.expressions.iter().enumerate() {
26033                if i > 0 {
26034                    self.write(", ");
26035                }
26036                self.generate_expression(expr)?;
26037            }
26038            self.write(")");
26039        }
26040        Ok(())
26041    }
26042
26043    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
26044        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
26045        self.write_keyword("CURRENT_DATETIME");
26046        if let Some(this) = &e.this {
26047            self.write("(");
26048            self.generate_expression(this)?;
26049            self.write(")");
26050        }
26051        Ok(())
26052    }
26053
26054    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
26055        // CURRENT_SCHEMA - no arguments
26056        self.write_keyword("CURRENT_SCHEMA");
26057        Ok(())
26058    }
26059
26060    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
26061        // CURRENT_SCHEMAS(include_implicit)
26062        self.write_keyword("CURRENT_SCHEMAS");
26063        self.write("(");
26064        if let Some(this) = &e.this {
26065            self.generate_expression(this)?;
26066        }
26067        self.write(")");
26068        Ok(())
26069    }
26070
26071    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
26072        // CURRENT_USER or CURRENT_USER()
26073        self.write_keyword("CURRENT_USER");
26074        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
26075        let needs_parens = e.this.is_some()
26076            || matches!(
26077                self.config.dialect,
26078                Some(DialectType::Snowflake)
26079                    | Some(DialectType::Spark)
26080                    | Some(DialectType::Hive)
26081                    | Some(DialectType::DuckDB)
26082                    | Some(DialectType::BigQuery)
26083                    | Some(DialectType::MySQL)
26084                    | Some(DialectType::Databricks)
26085            );
26086        if needs_parens {
26087            self.write("()");
26088        }
26089        Ok(())
26090    }
26091
26092    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26093        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26094        if self.config.dialect == Some(DialectType::Solr) {
26095            self.generate_expression(&e.this)?;
26096            self.write(" ");
26097            self.write_keyword("OR");
26098            self.write(" ");
26099            self.generate_expression(&e.expression)?;
26100        } else if self.config.dialect == Some(DialectType::MySQL) {
26101            self.generate_mysql_concat_from_dpipe(e)?;
26102        } else {
26103            // String concatenation: this || expression
26104            self.generate_expression(&e.this)?;
26105            self.write(" || ");
26106            self.generate_expression(&e.expression)?;
26107        }
26108        Ok(())
26109    }
26110
26111    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26112        // DATABLOCKSIZE=... (Teradata)
26113        self.write_keyword("DATABLOCKSIZE");
26114        self.write("=");
26115        if let Some(size) = e.size {
26116            self.write(&size.to_string());
26117            if let Some(units) = &e.units {
26118                self.write_space();
26119                self.generate_expression(units)?;
26120            }
26121        } else if e.minimum.is_some() {
26122            self.write_keyword("MINIMUM");
26123        } else if e.maximum.is_some() {
26124            self.write_keyword("MAXIMUM");
26125        } else if e.default.is_some() {
26126            self.write_keyword("DEFAULT");
26127        }
26128        Ok(())
26129    }
26130
26131    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26132        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26133        self.write_keyword("DATA_DELETION");
26134        self.write("=");
26135
26136        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26137        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26138
26139        if is_on {
26140            self.write_keyword("ON");
26141            if has_options {
26142                self.write("(");
26143                let mut first = true;
26144                if let Some(filter_column) = &e.filter_column {
26145                    self.write_keyword("FILTER_COLUMN");
26146                    self.write("=");
26147                    self.generate_expression(filter_column)?;
26148                    first = false;
26149                }
26150                if let Some(retention_period) = &e.retention_period {
26151                    if !first {
26152                        self.write(", ");
26153                    }
26154                    self.write_keyword("RETENTION_PERIOD");
26155                    self.write("=");
26156                    self.generate_expression(retention_period)?;
26157                }
26158                self.write(")");
26159            }
26160        } else {
26161            self.write_keyword("OFF");
26162        }
26163        Ok(())
26164    }
26165
26166    /// Generate a Date function expression
26167    /// For Exasol: {d'value'} -> TO_DATE('value')
26168    /// For other dialects: DATE('value')
26169    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26170        use crate::dialects::DialectType;
26171        use crate::expressions::Literal;
26172
26173        match self.config.dialect {
26174            // Exasol uses TO_DATE for Date expressions
26175            Some(DialectType::Exasol) => {
26176                self.write_keyword("TO_DATE");
26177                self.write("(");
26178                // Extract the string value from the expression if it's a string literal
26179                match &e.this {
26180                    Expression::Literal(Literal::String(s)) => {
26181                        self.write("'");
26182                        self.write(s);
26183                        self.write("'");
26184                    }
26185                    _ => {
26186                        self.generate_expression(&e.this)?;
26187                    }
26188                }
26189                self.write(")");
26190            }
26191            // Standard: DATE(value)
26192            _ => {
26193                self.write_keyword("DATE");
26194                self.write("(");
26195                self.generate_expression(&e.this)?;
26196                self.write(")");
26197            }
26198        }
26199        Ok(())
26200    }
26201
26202    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26203        // DATE_BIN(interval, timestamp[, origin])
26204        self.write_keyword("DATE_BIN");
26205        self.write("(");
26206        self.generate_expression(&e.this)?;
26207        self.write(", ");
26208        self.generate_expression(&e.expression)?;
26209        if let Some(origin) = &e.origin {
26210            self.write(", ");
26211            self.generate_expression(origin)?;
26212        }
26213        self.write(")");
26214        Ok(())
26215    }
26216
26217    fn generate_date_format_column_constraint(
26218        &mut self,
26219        e: &DateFormatColumnConstraint,
26220    ) -> Result<()> {
26221        // FORMAT 'format_string' (Teradata)
26222        self.write_keyword("FORMAT");
26223        self.write_space();
26224        self.generate_expression(&e.this)?;
26225        Ok(())
26226    }
26227
26228    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26229        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26230        self.write_keyword("DATE_FROM_PARTS");
26231        self.write("(");
26232        let mut first = true;
26233        if let Some(year) = &e.year {
26234            self.generate_expression(year)?;
26235            first = false;
26236        }
26237        if let Some(month) = &e.month {
26238            if !first {
26239                self.write(", ");
26240            }
26241            self.generate_expression(month)?;
26242            first = false;
26243        }
26244        if let Some(day) = &e.day {
26245            if !first {
26246                self.write(", ");
26247            }
26248            self.generate_expression(day)?;
26249        }
26250        self.write(")");
26251        Ok(())
26252    }
26253
26254    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26255        // DATETIME(this) or DATETIME(this, expression)
26256        self.write_keyword("DATETIME");
26257        self.write("(");
26258        self.generate_expression(&e.this)?;
26259        if let Some(expr) = &e.expression {
26260            self.write(", ");
26261            self.generate_expression(expr)?;
26262        }
26263        self.write(")");
26264        Ok(())
26265    }
26266
26267    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26268        // DATETIME_ADD(this, expression, unit)
26269        self.write_keyword("DATETIME_ADD");
26270        self.write("(");
26271        self.generate_expression(&e.this)?;
26272        self.write(", ");
26273        self.generate_expression(&e.expression)?;
26274        if let Some(unit) = &e.unit {
26275            self.write(", ");
26276            self.write_keyword(unit);
26277        }
26278        self.write(")");
26279        Ok(())
26280    }
26281
26282    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26283        // DATETIME_DIFF(this, expression, unit)
26284        self.write_keyword("DATETIME_DIFF");
26285        self.write("(");
26286        self.generate_expression(&e.this)?;
26287        self.write(", ");
26288        self.generate_expression(&e.expression)?;
26289        if let Some(unit) = &e.unit {
26290            self.write(", ");
26291            self.write_keyword(unit);
26292        }
26293        self.write(")");
26294        Ok(())
26295    }
26296
26297    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26298        // DATETIME_SUB(this, expression, unit)
26299        self.write_keyword("DATETIME_SUB");
26300        self.write("(");
26301        self.generate_expression(&e.this)?;
26302        self.write(", ");
26303        self.generate_expression(&e.expression)?;
26304        if let Some(unit) = &e.unit {
26305            self.write(", ");
26306            self.write_keyword(unit);
26307        }
26308        self.write(")");
26309        Ok(())
26310    }
26311
26312    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26313        // DATETIME_TRUNC(this, unit, zone)
26314        self.write_keyword("DATETIME_TRUNC");
26315        self.write("(");
26316        self.generate_expression(&e.this)?;
26317        self.write(", ");
26318        self.write_keyword(&e.unit);
26319        if let Some(zone) = &e.zone {
26320            self.write(", ");
26321            self.generate_expression(zone)?;
26322        }
26323        self.write(")");
26324        Ok(())
26325    }
26326
26327    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26328        // DAYNAME(this)
26329        self.write_keyword("DAYNAME");
26330        self.write("(");
26331        self.generate_expression(&e.this)?;
26332        self.write(")");
26333        Ok(())
26334    }
26335
26336    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26337        // DECLARE var1 AS type1, var2 AS type2, ...
26338        self.write_keyword("DECLARE");
26339        self.write_space();
26340        for (i, expr) in e.expressions.iter().enumerate() {
26341            if i > 0 {
26342                self.write(", ");
26343            }
26344            self.generate_expression(expr)?;
26345        }
26346        Ok(())
26347    }
26348
26349    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26350        use crate::dialects::DialectType;
26351
26352        // variable TYPE [DEFAULT default]
26353        self.generate_expression(&e.this)?;
26354        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26355        for name in &e.additional_names {
26356            self.write(", ");
26357            self.generate_expression(name)?;
26358        }
26359        if let Some(kind) = &e.kind {
26360            self.write_space();
26361            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26362            // TSQL: Always includes AS (normalization)
26363            // Others: Include AS if present in original
26364            match self.config.dialect {
26365                Some(DialectType::BigQuery) => {
26366                    self.write(kind);
26367                }
26368                Some(DialectType::TSQL) => {
26369                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26370                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26371                    // NONCLUSTERED, or INDEX constraints
26372                    let is_complex_table = kind.starts_with("TABLE")
26373                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26374
26375                    if is_complex_table {
26376                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26377                        self.write(kind);
26378                    } else {
26379                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26380                        if !kind.starts_with("CURSOR") {
26381                            self.write_keyword("AS");
26382                            self.write_space();
26383                        }
26384                        // Normalize INT to INTEGER for TSQL DECLARE statements
26385                        if kind == "INT" {
26386                            self.write("INTEGER");
26387                        } else if kind.starts_with("TABLE") {
26388                            // Normalize INT to INTEGER inside TABLE column definitions
26389                            let normalized = kind
26390                                .replace(" INT ", " INTEGER ")
26391                                .replace(" INT,", " INTEGER,")
26392                                .replace(" INT)", " INTEGER)")
26393                                .replace("(INT ", "(INTEGER ");
26394                            self.write(&normalized);
26395                        } else {
26396                            self.write(kind);
26397                        }
26398                    }
26399                }
26400                _ => {
26401                    if e.has_as {
26402                        self.write_keyword("AS");
26403                        self.write_space();
26404                    }
26405                    self.write(kind);
26406                }
26407            }
26408        }
26409        if let Some(default) = &e.default {
26410            // BigQuery uses DEFAULT, others use =
26411            match self.config.dialect {
26412                Some(DialectType::BigQuery) => {
26413                    self.write_space();
26414                    self.write_keyword("DEFAULT");
26415                    self.write_space();
26416                }
26417                _ => {
26418                    self.write(" = ");
26419                }
26420            }
26421            self.generate_expression(default)?;
26422        }
26423        Ok(())
26424    }
26425
26426    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26427        // DECODE(expr, search1, result1, search2, result2, ..., default)
26428        self.write_keyword("DECODE");
26429        self.write("(");
26430        for (i, expr) in e.expressions.iter().enumerate() {
26431            if i > 0 {
26432                self.write(", ");
26433            }
26434            self.generate_expression(expr)?;
26435        }
26436        self.write(")");
26437        Ok(())
26438    }
26439
26440    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26441        // DECOMPRESS(expr, 'method')
26442        self.write_keyword("DECOMPRESS");
26443        self.write("(");
26444        self.generate_expression(&e.this)?;
26445        self.write(", '");
26446        self.write(&e.method);
26447        self.write("')");
26448        Ok(())
26449    }
26450
26451    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26452        // DECOMPRESS(expr, 'method')
26453        self.write_keyword("DECOMPRESS");
26454        self.write("(");
26455        self.generate_expression(&e.this)?;
26456        self.write(", '");
26457        self.write(&e.method);
26458        self.write("')");
26459        Ok(())
26460    }
26461
26462    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26463        // DECRYPT(value, passphrase [, aad [, algorithm]])
26464        self.write_keyword("DECRYPT");
26465        self.write("(");
26466        self.generate_expression(&e.this)?;
26467        if let Some(passphrase) = &e.passphrase {
26468            self.write(", ");
26469            self.generate_expression(passphrase)?;
26470        }
26471        if let Some(aad) = &e.aad {
26472            self.write(", ");
26473            self.generate_expression(aad)?;
26474        }
26475        if let Some(method) = &e.encryption_method {
26476            self.write(", ");
26477            self.generate_expression(method)?;
26478        }
26479        self.write(")");
26480        Ok(())
26481    }
26482
26483    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26484        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26485        self.write_keyword("DECRYPT_RAW");
26486        self.write("(");
26487        self.generate_expression(&e.this)?;
26488        if let Some(key) = &e.key {
26489            self.write(", ");
26490            self.generate_expression(key)?;
26491        }
26492        if let Some(iv) = &e.iv {
26493            self.write(", ");
26494            self.generate_expression(iv)?;
26495        }
26496        if let Some(aad) = &e.aad {
26497            self.write(", ");
26498            self.generate_expression(aad)?;
26499        }
26500        if let Some(method) = &e.encryption_method {
26501            self.write(", ");
26502            self.generate_expression(method)?;
26503        }
26504        self.write(")");
26505        Ok(())
26506    }
26507
26508    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26509        // DEFINER = user
26510        self.write_keyword("DEFINER");
26511        self.write(" = ");
26512        self.generate_expression(&e.this)?;
26513        Ok(())
26514    }
26515
26516    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26517        // Python: DETACH[DATABASE IF EXISTS] this
26518        self.write_keyword("DETACH");
26519        if e.exists {
26520            self.write_keyword(" DATABASE IF EXISTS");
26521        }
26522        self.write_space();
26523        self.generate_expression(&e.this)?;
26524        Ok(())
26525    }
26526
26527    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26528        let property_name = match e.this.as_ref() {
26529            Expression::Identifier(id) => id.name.as_str(),
26530            Expression::Var(v) => v.this.as_str(),
26531            _ => "DICTIONARY",
26532        };
26533        self.write_keyword(property_name);
26534        self.write("(");
26535        self.write(&e.kind);
26536        if let Some(settings) = &e.settings {
26537            self.write("(");
26538            if let Expression::Tuple(t) = settings.as_ref() {
26539                if self.config.pretty && !t.expressions.is_empty() {
26540                    self.write_newline();
26541                    self.indent_level += 1;
26542                    for (i, pair) in t.expressions.iter().enumerate() {
26543                        if i > 0 {
26544                            self.write(",");
26545                            self.write_newline();
26546                        }
26547                        self.write_indent();
26548                        if let Expression::Tuple(pair_tuple) = pair {
26549                            if let Some(k) = pair_tuple.expressions.first() {
26550                                self.generate_expression(k)?;
26551                            }
26552                            if let Some(v) = pair_tuple.expressions.get(1) {
26553                                self.write(" ");
26554                                self.generate_expression(v)?;
26555                            }
26556                        } else {
26557                            self.generate_expression(pair)?;
26558                        }
26559                    }
26560                    self.indent_level -= 1;
26561                    self.write_newline();
26562                    self.write_indent();
26563                } else {
26564                    for (i, pair) in t.expressions.iter().enumerate() {
26565                        if i > 0 {
26566                            self.write(", ");
26567                        }
26568                        if let Expression::Tuple(pair_tuple) = pair {
26569                            if let Some(k) = pair_tuple.expressions.first() {
26570                                self.generate_expression(k)?;
26571                            }
26572                            if let Some(v) = pair_tuple.expressions.get(1) {
26573                                self.write(" ");
26574                                self.generate_expression(v)?;
26575                            }
26576                        } else {
26577                            self.generate_expression(pair)?;
26578                        }
26579                    }
26580                }
26581            } else {
26582                self.generate_expression(settings)?;
26583            }
26584            self.write(")");
26585        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26586            self.write("()");
26587        }
26588        self.write(")");
26589        Ok(())
26590    }
26591
26592    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26593        let property_name = match e.this.as_ref() {
26594            Expression::Identifier(id) => id.name.as_str(),
26595            Expression::Var(v) => v.this.as_str(),
26596            _ => "RANGE",
26597        };
26598        self.write_keyword(property_name);
26599        self.write("(");
26600        if let Some(min) = &e.min {
26601            self.write_keyword("MIN");
26602            self.write_space();
26603            self.generate_expression(min)?;
26604        }
26605        if let Some(max) = &e.max {
26606            self.write_space();
26607            self.write_keyword("MAX");
26608            self.write_space();
26609            self.generate_expression(max)?;
26610        }
26611        self.write(")");
26612        Ok(())
26613    }
26614
26615    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26616        // Python: {local}DIRECTORY {this}{row_format}
26617        if e.local.is_some() {
26618            self.write_keyword("LOCAL ");
26619        }
26620        self.write_keyword("DIRECTORY");
26621        self.write_space();
26622        self.generate_expression(&e.this)?;
26623        if let Some(row_format) = &e.row_format {
26624            self.write_space();
26625            self.generate_expression(row_format)?;
26626        }
26627        Ok(())
26628    }
26629
26630    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26631        // Redshift: DISTKEY(column)
26632        self.write_keyword("DISTKEY");
26633        self.write("(");
26634        self.generate_expression(&e.this)?;
26635        self.write(")");
26636        Ok(())
26637    }
26638
26639    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26640        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26641        self.write_keyword("DISTSTYLE");
26642        self.write_space();
26643        self.generate_expression(&e.this)?;
26644        Ok(())
26645    }
26646
26647    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26648        // Python: "DISTRIBUTE BY" expressions
26649        self.write_keyword("DISTRIBUTE BY");
26650        self.write_space();
26651        for (i, expr) in e.expressions.iter().enumerate() {
26652            if i > 0 {
26653                self.write(", ");
26654            }
26655            self.generate_expression(expr)?;
26656        }
26657        Ok(())
26658    }
26659
26660    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26661        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26662        self.write_keyword("DISTRIBUTED BY");
26663        self.write_space();
26664        self.write(&e.kind);
26665        if !e.expressions.is_empty() {
26666            self.write(" (");
26667            for (i, expr) in e.expressions.iter().enumerate() {
26668                if i > 0 {
26669                    self.write(", ");
26670                }
26671                self.generate_expression(expr)?;
26672            }
26673            self.write(")");
26674        }
26675        if let Some(buckets) = &e.buckets {
26676            self.write_space();
26677            self.write_keyword("BUCKETS");
26678            self.write_space();
26679            self.generate_expression(buckets)?;
26680        }
26681        if let Some(order) = &e.order {
26682            self.write_space();
26683            self.generate_expression(order)?;
26684        }
26685        Ok(())
26686    }
26687
26688    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26689        // DOT_PRODUCT(vector1, vector2)
26690        self.write_keyword("DOT_PRODUCT");
26691        self.write("(");
26692        self.generate_expression(&e.this)?;
26693        self.write(", ");
26694        self.generate_expression(&e.expression)?;
26695        self.write(")");
26696        Ok(())
26697    }
26698
26699    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26700        // Python: DROP{IF EXISTS }expressions
26701        self.write_keyword("DROP");
26702        if e.exists {
26703            self.write_keyword(" IF EXISTS ");
26704        } else {
26705            self.write_space();
26706        }
26707        for (i, expr) in e.expressions.iter().enumerate() {
26708            if i > 0 {
26709                self.write(", ");
26710            }
26711            self.generate_expression(expr)?;
26712        }
26713        Ok(())
26714    }
26715
26716    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26717        // Python: DUPLICATE KEY (expressions)
26718        self.write_keyword("DUPLICATE KEY");
26719        self.write(" (");
26720        for (i, expr) in e.expressions.iter().enumerate() {
26721            if i > 0 {
26722                self.write(", ");
26723            }
26724            self.generate_expression(expr)?;
26725        }
26726        self.write(")");
26727        Ok(())
26728    }
26729
26730    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26731        // ELT(index, str1, str2, ...)
26732        self.write_keyword("ELT");
26733        self.write("(");
26734        self.generate_expression(&e.this)?;
26735        for expr in &e.expressions {
26736            self.write(", ");
26737            self.generate_expression(expr)?;
26738        }
26739        self.write(")");
26740        Ok(())
26741    }
26742
26743    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26744        // ENCODE(string, charset)
26745        self.write_keyword("ENCODE");
26746        self.write("(");
26747        self.generate_expression(&e.this)?;
26748        if let Some(charset) = &e.charset {
26749            self.write(", ");
26750            self.generate_expression(charset)?;
26751        }
26752        self.write(")");
26753        Ok(())
26754    }
26755
26756    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26757        // Python: [KEY ]ENCODE this [properties]
26758        if e.key.is_some() {
26759            self.write_keyword("KEY ");
26760        }
26761        self.write_keyword("ENCODE");
26762        self.write_space();
26763        self.generate_expression(&e.this)?;
26764        if !e.properties.is_empty() {
26765            self.write(" (");
26766            for (i, prop) in e.properties.iter().enumerate() {
26767                if i > 0 {
26768                    self.write(", ");
26769                }
26770                self.generate_expression(prop)?;
26771            }
26772            self.write(")");
26773        }
26774        Ok(())
26775    }
26776
26777    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26778        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26779        self.write_keyword("ENCRYPT");
26780        self.write("(");
26781        self.generate_expression(&e.this)?;
26782        if let Some(passphrase) = &e.passphrase {
26783            self.write(", ");
26784            self.generate_expression(passphrase)?;
26785        }
26786        if let Some(aad) = &e.aad {
26787            self.write(", ");
26788            self.generate_expression(aad)?;
26789        }
26790        if let Some(method) = &e.encryption_method {
26791            self.write(", ");
26792            self.generate_expression(method)?;
26793        }
26794        self.write(")");
26795        Ok(())
26796    }
26797
26798    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26799        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26800        self.write_keyword("ENCRYPT_RAW");
26801        self.write("(");
26802        self.generate_expression(&e.this)?;
26803        if let Some(key) = &e.key {
26804            self.write(", ");
26805            self.generate_expression(key)?;
26806        }
26807        if let Some(iv) = &e.iv {
26808            self.write(", ");
26809            self.generate_expression(iv)?;
26810        }
26811        if let Some(aad) = &e.aad {
26812            self.write(", ");
26813            self.generate_expression(aad)?;
26814        }
26815        if let Some(method) = &e.encryption_method {
26816            self.write(", ");
26817            self.generate_expression(method)?;
26818        }
26819        self.write(")");
26820        Ok(())
26821    }
26822
26823    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26824        // MySQL: ENGINE = InnoDB
26825        self.write_keyword("ENGINE");
26826        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26827            self.write("=");
26828        } else {
26829            self.write(" = ");
26830        }
26831        self.generate_expression(&e.this)?;
26832        Ok(())
26833    }
26834
26835    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26836        // ENVIRONMENT (expressions)
26837        self.write_keyword("ENVIRONMENT");
26838        self.write(" (");
26839        for (i, expr) in e.expressions.iter().enumerate() {
26840            if i > 0 {
26841                self.write(", ");
26842            }
26843            self.generate_expression(expr)?;
26844        }
26845        self.write(")");
26846        Ok(())
26847    }
26848
26849    fn generate_ephemeral_column_constraint(
26850        &mut self,
26851        e: &EphemeralColumnConstraint,
26852    ) -> Result<()> {
26853        // MySQL: EPHEMERAL [expr]
26854        self.write_keyword("EPHEMERAL");
26855        if let Some(this) = &e.this {
26856            self.write_space();
26857            self.generate_expression(this)?;
26858        }
26859        Ok(())
26860    }
26861
26862    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26863        // Snowflake: EQUAL_NULL(a, b)
26864        self.write_keyword("EQUAL_NULL");
26865        self.write("(");
26866        self.generate_expression(&e.this)?;
26867        self.write(", ");
26868        self.generate_expression(&e.expression)?;
26869        self.write(")");
26870        Ok(())
26871    }
26872
26873    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26874        use crate::dialects::DialectType;
26875
26876        // PostgreSQL uses <-> operator syntax
26877        match self.config.dialect {
26878            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26879                self.generate_expression(&e.this)?;
26880                self.write(" <-> ");
26881                self.generate_expression(&e.expression)?;
26882            }
26883            _ => {
26884                // Other dialects use EUCLIDEAN_DISTANCE function
26885                self.write_keyword("EUCLIDEAN_DISTANCE");
26886                self.write("(");
26887                self.generate_expression(&e.this)?;
26888                self.write(", ");
26889                self.generate_expression(&e.expression)?;
26890                self.write(")");
26891            }
26892        }
26893        Ok(())
26894    }
26895
26896    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26897        // EXECUTE AS CALLER|OWNER|user
26898        self.write_keyword("EXECUTE AS");
26899        self.write_space();
26900        self.generate_expression(&e.this)?;
26901        Ok(())
26902    }
26903
26904    fn generate_export(&mut self, e: &Export) -> Result<()> {
26905        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26906        self.write_keyword("EXPORT DATA");
26907        if let Some(connection) = &e.connection {
26908            self.write_space();
26909            self.write_keyword("WITH CONNECTION");
26910            self.write_space();
26911            self.generate_expression(connection)?;
26912        }
26913        if !e.options.is_empty() {
26914            self.write_space();
26915            self.generate_options_clause(&e.options)?;
26916        }
26917        self.write_space();
26918        self.write_keyword("AS");
26919        self.write_space();
26920        self.generate_expression(&e.this)?;
26921        Ok(())
26922    }
26923
26924    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26925        // EXTERNAL [this]
26926        self.write_keyword("EXTERNAL");
26927        if let Some(this) = &e.this {
26928            self.write_space();
26929            self.generate_expression(this)?;
26930        }
26931        Ok(())
26932    }
26933
26934    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26935        // Python: {no}FALLBACK{protection}
26936        if e.no.is_some() {
26937            self.write_keyword("NO ");
26938        }
26939        self.write_keyword("FALLBACK");
26940        if e.protection.is_some() {
26941            self.write_keyword(" PROTECTION");
26942        }
26943        Ok(())
26944    }
26945
26946    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26947        // BigQuery: FARM_FINGERPRINT(value)
26948        self.write_keyword("FARM_FINGERPRINT");
26949        self.write("(");
26950        for (i, expr) in e.expressions.iter().enumerate() {
26951            if i > 0 {
26952                self.write(", ");
26953            }
26954            self.generate_expression(expr)?;
26955        }
26956        self.write(")");
26957        Ok(())
26958    }
26959
26960    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26961        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26962        self.write_keyword("FEATURES_AT_TIME");
26963        self.write("(");
26964        self.generate_expression(&e.this)?;
26965        if let Some(time) = &e.time {
26966            self.write(", ");
26967            self.generate_expression(time)?;
26968        }
26969        if let Some(num_rows) = &e.num_rows {
26970            self.write(", ");
26971            self.generate_expression(num_rows)?;
26972        }
26973        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26974            self.write(", ");
26975            self.generate_expression(ignore_nulls)?;
26976        }
26977        self.write(")");
26978        Ok(())
26979    }
26980
26981    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26982        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26983        let use_limit = !e.percent
26984            && !e.with_ties
26985            && e.count.is_some()
26986            && matches!(
26987                self.config.dialect,
26988                Some(DialectType::Spark)
26989                    | Some(DialectType::Hive)
26990                    | Some(DialectType::DuckDB)
26991                    | Some(DialectType::SQLite)
26992                    | Some(DialectType::MySQL)
26993                    | Some(DialectType::BigQuery)
26994                    | Some(DialectType::Databricks)
26995                    | Some(DialectType::StarRocks)
26996                    | Some(DialectType::Doris)
26997                    | Some(DialectType::Athena)
26998                    | Some(DialectType::ClickHouse)
26999            );
27000
27001        if use_limit {
27002            self.write_keyword("LIMIT");
27003            self.write_space();
27004            self.generate_expression(e.count.as_ref().unwrap())?;
27005            return Ok(());
27006        }
27007
27008        // Python: FETCH direction count limit_options
27009        self.write_keyword("FETCH");
27010        if !e.direction.is_empty() {
27011            self.write_space();
27012            self.write_keyword(&e.direction);
27013        }
27014        if let Some(count) = &e.count {
27015            self.write_space();
27016            self.generate_expression(count)?;
27017        }
27018        // Generate PERCENT, ROWS, WITH TIES/ONLY
27019        if e.percent {
27020            self.write_keyword(" PERCENT");
27021        }
27022        if e.rows {
27023            self.write_keyword(" ROWS");
27024        }
27025        if e.with_ties {
27026            self.write_keyword(" WITH TIES");
27027        } else if e.rows {
27028            self.write_keyword(" ONLY");
27029        } else {
27030            self.write_keyword(" ROWS ONLY");
27031        }
27032        Ok(())
27033    }
27034
27035    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
27036        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
27037        // For Spark/Databricks without hive_format: USING this
27038        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
27039        if e.hive_format.is_some() {
27040            // Hive format: STORED AS ...
27041            self.write_keyword("STORED AS");
27042            self.write_space();
27043            if let Some(this) = &e.this {
27044                // Uppercase the format name (e.g., parquet -> PARQUET)
27045                if let Expression::Identifier(id) = this.as_ref() {
27046                    self.write_keyword(&id.name.to_uppercase());
27047                } else {
27048                    self.generate_expression(this)?;
27049                }
27050            }
27051        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
27052            // Hive: STORED AS format
27053            self.write_keyword("STORED AS");
27054            self.write_space();
27055            if let Some(this) = &e.this {
27056                if let Expression::Identifier(id) = this.as_ref() {
27057                    self.write_keyword(&id.name.to_uppercase());
27058                } else {
27059                    self.generate_expression(this)?;
27060                }
27061            }
27062        } else if matches!(
27063            self.config.dialect,
27064            Some(DialectType::Spark) | Some(DialectType::Databricks)
27065        ) {
27066            // Spark/Databricks: USING format (e.g., USING DELTA)
27067            self.write_keyword("USING");
27068            self.write_space();
27069            if let Some(this) = &e.this {
27070                self.generate_expression(this)?;
27071            }
27072        } else {
27073            // Snowflake/standard format
27074            self.write_keyword("FILE_FORMAT");
27075            self.write(" = ");
27076            if let Some(this) = &e.this {
27077                self.generate_expression(this)?;
27078            } else if !e.expressions.is_empty() {
27079                self.write("(");
27080                for (i, expr) in e.expressions.iter().enumerate() {
27081                    if i > 0 {
27082                        self.write(", ");
27083                    }
27084                    self.generate_expression(expr)?;
27085                }
27086                self.write(")");
27087            }
27088        }
27089        Ok(())
27090    }
27091
27092    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27093        // agg_func FILTER(WHERE condition)
27094        self.generate_expression(&e.this)?;
27095        self.write_space();
27096        self.write_keyword("FILTER");
27097        self.write("(");
27098        self.write_keyword("WHERE");
27099        self.write_space();
27100        self.generate_expression(&e.expression)?;
27101        self.write(")");
27102        Ok(())
27103    }
27104
27105    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27106        // FLOAT64(this) or FLOAT64(this, expression)
27107        self.write_keyword("FLOAT64");
27108        self.write("(");
27109        self.generate_expression(&e.this)?;
27110        if let Some(expr) = &e.expression {
27111            self.write(", ");
27112            self.generate_expression(expr)?;
27113        }
27114        self.write(")");
27115        Ok(())
27116    }
27117
27118    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27119        // FOR this DO expression
27120        self.write_keyword("FOR");
27121        self.write_space();
27122        self.generate_expression(&e.this)?;
27123        self.write_space();
27124        self.write_keyword("DO");
27125        self.write_space();
27126        self.generate_expression(&e.expression)?;
27127        Ok(())
27128    }
27129
27130    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27131        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27132        self.write_keyword("FOREIGN KEY");
27133        if !e.expressions.is_empty() {
27134            self.write(" (");
27135            for (i, expr) in e.expressions.iter().enumerate() {
27136                if i > 0 {
27137                    self.write(", ");
27138                }
27139                self.generate_expression(expr)?;
27140            }
27141            self.write(")");
27142        }
27143        if let Some(reference) = &e.reference {
27144            self.write_space();
27145            self.generate_expression(reference)?;
27146        }
27147        if let Some(delete) = &e.delete {
27148            self.write_space();
27149            self.write_keyword("ON DELETE");
27150            self.write_space();
27151            self.generate_expression(delete)?;
27152        }
27153        if let Some(update) = &e.update {
27154            self.write_space();
27155            self.write_keyword("ON UPDATE");
27156            self.write_space();
27157            self.generate_expression(update)?;
27158        }
27159        if !e.options.is_empty() {
27160            self.write_space();
27161            for (i, opt) in e.options.iter().enumerate() {
27162                if i > 0 {
27163                    self.write_space();
27164                }
27165                self.generate_expression(opt)?;
27166            }
27167        }
27168        Ok(())
27169    }
27170
27171    fn generate_format(&mut self, e: &Format) -> Result<()> {
27172        // FORMAT(this, expressions...)
27173        self.write_keyword("FORMAT");
27174        self.write("(");
27175        self.generate_expression(&e.this)?;
27176        for expr in &e.expressions {
27177            self.write(", ");
27178            self.generate_expression(expr)?;
27179        }
27180        self.write(")");
27181        Ok(())
27182    }
27183
27184    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27185        // Teradata: column (FORMAT 'format_string')
27186        self.generate_expression(&e.this)?;
27187        self.write(" (");
27188        self.write_keyword("FORMAT");
27189        self.write(" '");
27190        self.write(&e.format);
27191        self.write("')");
27192        Ok(())
27193    }
27194
27195    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27196        // Python: FREESPACE=this[PERCENT]
27197        self.write_keyword("FREESPACE");
27198        self.write("=");
27199        self.generate_expression(&e.this)?;
27200        if e.percent.is_some() {
27201            self.write_keyword(" PERCENT");
27202        }
27203        Ok(())
27204    }
27205
27206    fn generate_from(&mut self, e: &From) -> Result<()> {
27207        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27208        self.write_keyword("FROM");
27209        self.write_space();
27210
27211        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27212        // But keep commas when TABLESAMPLE is present
27213        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27214        use crate::dialects::DialectType;
27215        let has_tablesample = e
27216            .expressions
27217            .iter()
27218            .any(|expr| matches!(expr, Expression::TableSample(_)));
27219        let is_cross_join_dialect = matches!(
27220            self.config.dialect,
27221            Some(DialectType::BigQuery)
27222                | Some(DialectType::Hive)
27223                | Some(DialectType::Spark)
27224                | Some(DialectType::Databricks)
27225                | Some(DialectType::SQLite)
27226                | Some(DialectType::ClickHouse)
27227        );
27228        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27229            && self.config.source_dialect == self.config.dialect;
27230        let source_is_cross_join_dialect2 = matches!(
27231            self.config.source_dialect,
27232            Some(DialectType::BigQuery)
27233                | Some(DialectType::Hive)
27234                | Some(DialectType::Spark)
27235                | Some(DialectType::Databricks)
27236                | Some(DialectType::SQLite)
27237                | Some(DialectType::ClickHouse)
27238        );
27239        let use_cross_join = !has_tablesample
27240            && is_cross_join_dialect
27241            && (source_is_same_as_target2
27242                || source_is_cross_join_dialect2
27243                || self.config.source_dialect.is_none());
27244
27245        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27246        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27247
27248        for (i, expr) in e.expressions.iter().enumerate() {
27249            if i > 0 {
27250                if use_cross_join {
27251                    self.write(" CROSS JOIN ");
27252                } else {
27253                    self.write(", ");
27254                }
27255            }
27256            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27257                self.write("(");
27258                self.generate_expression(expr)?;
27259                self.write(")");
27260            } else {
27261                self.generate_expression(expr)?;
27262            }
27263        }
27264        Ok(())
27265    }
27266
27267    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27268        // FROM_BASE(this, expression) - convert from base N
27269        self.write_keyword("FROM_BASE");
27270        self.write("(");
27271        self.generate_expression(&e.this)?;
27272        self.write(", ");
27273        self.generate_expression(&e.expression)?;
27274        self.write(")");
27275        Ok(())
27276    }
27277
27278    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27279        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27280        self.generate_expression(&e.this)?;
27281        if let Some(zone) = &e.zone {
27282            self.write_space();
27283            self.write_keyword("AT TIME ZONE");
27284            self.write_space();
27285            self.generate_expression(zone)?;
27286            self.write_space();
27287            self.write_keyword("AT TIME ZONE");
27288            self.write(" 'UTC'");
27289        }
27290        Ok(())
27291    }
27292
27293    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27294        // GAP_FILL(this, ts_column, bucket_width, ...)
27295        self.write_keyword("GAP_FILL");
27296        self.write("(");
27297        self.generate_expression(&e.this)?;
27298        if let Some(ts_column) = &e.ts_column {
27299            self.write(", ");
27300            self.generate_expression(ts_column)?;
27301        }
27302        if let Some(bucket_width) = &e.bucket_width {
27303            self.write(", ");
27304            self.generate_expression(bucket_width)?;
27305        }
27306        if let Some(partitioning_columns) = &e.partitioning_columns {
27307            self.write(", ");
27308            self.generate_expression(partitioning_columns)?;
27309        }
27310        if let Some(value_columns) = &e.value_columns {
27311            self.write(", ");
27312            self.generate_expression(value_columns)?;
27313        }
27314        self.write(")");
27315        Ok(())
27316    }
27317
27318    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27319        // GENERATE_DATE_ARRAY(start, end, step)
27320        self.write_keyword("GENERATE_DATE_ARRAY");
27321        self.write("(");
27322        let mut first = true;
27323        if let Some(start) = &e.start {
27324            self.generate_expression(start)?;
27325            first = false;
27326        }
27327        if let Some(end) = &e.end {
27328            if !first {
27329                self.write(", ");
27330            }
27331            self.generate_expression(end)?;
27332            first = false;
27333        }
27334        if let Some(step) = &e.step {
27335            if !first {
27336                self.write(", ");
27337            }
27338            self.generate_expression(step)?;
27339        }
27340        self.write(")");
27341        Ok(())
27342    }
27343
27344    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27345        // ML.GENERATE_EMBEDDING(model, content, params)
27346        self.write_keyword("ML.GENERATE_EMBEDDING");
27347        self.write("(");
27348        self.generate_expression(&e.this)?;
27349        self.write(", ");
27350        self.generate_expression(&e.expression)?;
27351        if let Some(params) = &e.params_struct {
27352            self.write(", ");
27353            self.generate_expression(params)?;
27354        }
27355        self.write(")");
27356        Ok(())
27357    }
27358
27359    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27360        // Dialect-specific function name
27361        let fn_name = match self.config.dialect {
27362            Some(DialectType::Presto)
27363            | Some(DialectType::Trino)
27364            | Some(DialectType::Athena)
27365            | Some(DialectType::Spark)
27366            | Some(DialectType::Databricks)
27367            | Some(DialectType::Hive) => "SEQUENCE",
27368            _ => "GENERATE_SERIES",
27369        };
27370        self.write_keyword(fn_name);
27371        self.write("(");
27372        let mut first = true;
27373        if let Some(start) = &e.start {
27374            self.generate_expression(start)?;
27375            first = false;
27376        }
27377        if let Some(end) = &e.end {
27378            if !first {
27379                self.write(", ");
27380            }
27381            self.generate_expression(end)?;
27382            first = false;
27383        }
27384        if let Some(step) = &e.step {
27385            if !first {
27386                self.write(", ");
27387            }
27388            // For Presto/Trino: convert WEEK intervals to DAY multiples
27389            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27390            if matches!(
27391                self.config.dialect,
27392                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27393            ) {
27394                if let Some(converted) = self.convert_week_interval_to_day(step) {
27395                    self.generate_expression(&converted)?;
27396                } else {
27397                    self.generate_expression(step)?;
27398                }
27399            } else {
27400                self.generate_expression(step)?;
27401            }
27402        }
27403        self.write(")");
27404        Ok(())
27405    }
27406
27407    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27408    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27409    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27410        use crate::expressions::*;
27411        if let Expression::Interval(ref iv) = expr {
27412            // Check for structured WEEK unit
27413            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27414                unit: IntervalUnit::Week,
27415                ..
27416            }) = &iv.unit
27417            {
27418                // Value is in iv.this
27419                let count = match &iv.this {
27420                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27421                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27422                    _ => return None,
27423                };
27424                (true, count)
27425            } else if iv.unit.is_none() {
27426                // Check for string-encoded interval like "1 WEEK"
27427                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27428                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27429                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27430                        (true, parts[0].to_string())
27431                    } else {
27432                        (false, String::new())
27433                    }
27434                } else {
27435                    (false, String::new())
27436                }
27437            } else {
27438                (false, String::new())
27439            };
27440
27441            if is_week {
27442                // Build: (N * INTERVAL '7' DAY)
27443                let count_expr = Expression::Literal(Literal::Number(count_str));
27444                let day_interval = Expression::Interval(Box::new(Interval {
27445                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27446                    unit: Some(IntervalUnitSpec::Simple {
27447                        unit: IntervalUnit::Day,
27448                        use_plural: false,
27449                    }),
27450                }));
27451                let mul = Expression::Mul(Box::new(BinaryOp {
27452                    left: count_expr,
27453                    right: day_interval,
27454                    left_comments: vec![],
27455                    operator_comments: vec![],
27456                    trailing_comments: vec![],
27457                    inferred_type: None,
27458                }));
27459                return Some(Expression::Paren(Box::new(Paren {
27460                    this: mul,
27461                    trailing_comments: vec![],
27462                })));
27463            }
27464        }
27465        None
27466    }
27467
27468    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27469        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27470        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27471        self.write("(");
27472        let mut first = true;
27473        if let Some(start) = &e.start {
27474            self.generate_expression(start)?;
27475            first = false;
27476        }
27477        if let Some(end) = &e.end {
27478            if !first {
27479                self.write(", ");
27480            }
27481            self.generate_expression(end)?;
27482            first = false;
27483        }
27484        if let Some(step) = &e.step {
27485            if !first {
27486                self.write(", ");
27487            }
27488            self.generate_expression(step)?;
27489        }
27490        self.write(")");
27491        Ok(())
27492    }
27493
27494    fn generate_generated_as_identity_column_constraint(
27495        &mut self,
27496        e: &GeneratedAsIdentityColumnConstraint,
27497    ) -> Result<()> {
27498        use crate::dialects::DialectType;
27499
27500        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27501        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27502            self.write_keyword("AUTOINCREMENT");
27503            if let Some(start) = &e.start {
27504                self.write_keyword(" START ");
27505                self.generate_expression(start)?;
27506            }
27507            if let Some(increment) = &e.increment {
27508                self.write_keyword(" INCREMENT ");
27509                self.generate_expression(increment)?;
27510            }
27511            return Ok(());
27512        }
27513
27514        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27515        self.write_keyword("GENERATED");
27516        if let Some(this) = &e.this {
27517            // Check if it's a truthy boolean expression
27518            if let Expression::Boolean(b) = this.as_ref() {
27519                if b.value {
27520                    self.write_keyword(" ALWAYS");
27521                } else {
27522                    self.write_keyword(" BY DEFAULT");
27523                    if e.on_null.is_some() {
27524                        self.write_keyword(" ON NULL");
27525                    }
27526                }
27527            } else {
27528                self.write_keyword(" ALWAYS");
27529            }
27530        }
27531        self.write_keyword(" AS IDENTITY");
27532        // Add sequence options if any
27533        let has_options = e.start.is_some()
27534            || e.increment.is_some()
27535            || e.minvalue.is_some()
27536            || e.maxvalue.is_some();
27537        if has_options {
27538            self.write(" (");
27539            let mut first = true;
27540            if let Some(start) = &e.start {
27541                self.write_keyword("START WITH ");
27542                self.generate_expression(start)?;
27543                first = false;
27544            }
27545            if let Some(increment) = &e.increment {
27546                if !first {
27547                    self.write(" ");
27548                }
27549                self.write_keyword("INCREMENT BY ");
27550                self.generate_expression(increment)?;
27551                first = false;
27552            }
27553            if let Some(minvalue) = &e.minvalue {
27554                if !first {
27555                    self.write(" ");
27556                }
27557                self.write_keyword("MINVALUE ");
27558                self.generate_expression(minvalue)?;
27559                first = false;
27560            }
27561            if let Some(maxvalue) = &e.maxvalue {
27562                if !first {
27563                    self.write(" ");
27564                }
27565                self.write_keyword("MAXVALUE ");
27566                self.generate_expression(maxvalue)?;
27567            }
27568            self.write(")");
27569        }
27570        Ok(())
27571    }
27572
27573    fn generate_generated_as_row_column_constraint(
27574        &mut self,
27575        e: &GeneratedAsRowColumnConstraint,
27576    ) -> Result<()> {
27577        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27578        self.write_keyword("GENERATED ALWAYS AS ROW ");
27579        if e.start.is_some() {
27580            self.write_keyword("START");
27581        } else {
27582            self.write_keyword("END");
27583        }
27584        if e.hidden.is_some() {
27585            self.write_keyword(" HIDDEN");
27586        }
27587        Ok(())
27588    }
27589
27590    fn generate_get(&mut self, e: &Get) -> Result<()> {
27591        // GET this target properties
27592        self.write_keyword("GET");
27593        self.write_space();
27594        self.generate_expression(&e.this)?;
27595        if let Some(target) = &e.target {
27596            self.write_space();
27597            self.generate_expression(target)?;
27598        }
27599        for prop in &e.properties {
27600            self.write_space();
27601            self.generate_expression(prop)?;
27602        }
27603        Ok(())
27604    }
27605
27606    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27607        // GetExtract generates bracket access: this[expression]
27608        self.generate_expression(&e.this)?;
27609        self.write("[");
27610        self.generate_expression(&e.expression)?;
27611        self.write("]");
27612        Ok(())
27613    }
27614
27615    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27616        // GETBIT(this, expression) or GET_BIT(this, expression)
27617        self.write_keyword("GETBIT");
27618        self.write("(");
27619        self.generate_expression(&e.this)?;
27620        self.write(", ");
27621        self.generate_expression(&e.expression)?;
27622        self.write(")");
27623        Ok(())
27624    }
27625
27626    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27627        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27628        if e.is_role {
27629            self.write_keyword("ROLE");
27630            self.write_space();
27631        } else if e.is_group {
27632            self.write_keyword("GROUP");
27633            self.write_space();
27634        }
27635        self.write(&e.name.name);
27636        Ok(())
27637    }
27638
27639    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27640        // privilege(columns) or just privilege
27641        self.generate_expression(&e.this)?;
27642        if !e.expressions.is_empty() {
27643            self.write("(");
27644            for (i, expr) in e.expressions.iter().enumerate() {
27645                if i > 0 {
27646                    self.write(", ");
27647                }
27648                self.generate_expression(expr)?;
27649            }
27650            self.write(")");
27651        }
27652        Ok(())
27653    }
27654
27655    fn generate_group(&mut self, e: &Group) -> Result<()> {
27656        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27657        self.write_keyword("GROUP BY");
27658        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27659        match e.all {
27660            Some(true) => {
27661                self.write_space();
27662                self.write_keyword("ALL");
27663            }
27664            Some(false) => {
27665                self.write_space();
27666                self.write_keyword("DISTINCT");
27667            }
27668            None => {}
27669        }
27670        if !e.expressions.is_empty() {
27671            self.write_space();
27672            for (i, expr) in e.expressions.iter().enumerate() {
27673                if i > 0 {
27674                    self.write(", ");
27675                }
27676                self.generate_expression(expr)?;
27677            }
27678        }
27679        // Handle CUBE, ROLLUP, GROUPING SETS
27680        if let Some(cube) = &e.cube {
27681            if !e.expressions.is_empty() {
27682                self.write(", ");
27683            } else {
27684                self.write_space();
27685            }
27686            self.generate_expression(cube)?;
27687        }
27688        if let Some(rollup) = &e.rollup {
27689            if !e.expressions.is_empty() || e.cube.is_some() {
27690                self.write(", ");
27691            } else {
27692                self.write_space();
27693            }
27694            self.generate_expression(rollup)?;
27695        }
27696        if let Some(grouping_sets) = &e.grouping_sets {
27697            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27698                self.write(", ");
27699            } else {
27700                self.write_space();
27701            }
27702            self.generate_expression(grouping_sets)?;
27703        }
27704        if let Some(totals) = &e.totals {
27705            self.write_space();
27706            self.write_keyword("WITH TOTALS");
27707            self.generate_expression(totals)?;
27708        }
27709        Ok(())
27710    }
27711
27712    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27713        // GROUP BY expressions
27714        self.write_keyword("GROUP BY");
27715        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27716        match e.all {
27717            Some(true) => {
27718                self.write_space();
27719                self.write_keyword("ALL");
27720            }
27721            Some(false) => {
27722                self.write_space();
27723                self.write_keyword("DISTINCT");
27724            }
27725            None => {}
27726        }
27727
27728        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27729        // These are represented as Cube/Rollup expressions with empty expressions at the end
27730        let mut trailing_cube = false;
27731        let mut trailing_rollup = false;
27732        let mut regular_expressions: Vec<&Expression> = Vec::new();
27733
27734        for expr in &e.expressions {
27735            match expr {
27736                Expression::Cube(c) if c.expressions.is_empty() => {
27737                    trailing_cube = true;
27738                }
27739                Expression::Rollup(r) if r.expressions.is_empty() => {
27740                    trailing_rollup = true;
27741                }
27742                _ => {
27743                    regular_expressions.push(expr);
27744                }
27745            }
27746        }
27747
27748        // In pretty mode, put columns on separate lines
27749        if self.config.pretty {
27750            self.write_newline();
27751            self.indent_level += 1;
27752            for (i, expr) in regular_expressions.iter().enumerate() {
27753                if i > 0 {
27754                    self.write(",");
27755                    self.write_newline();
27756                }
27757                self.write_indent();
27758                self.generate_expression(expr)?;
27759            }
27760            self.indent_level -= 1;
27761        } else {
27762            self.write_space();
27763            for (i, expr) in regular_expressions.iter().enumerate() {
27764                if i > 0 {
27765                    self.write(", ");
27766                }
27767                self.generate_expression(expr)?;
27768            }
27769        }
27770
27771        // Output trailing WITH CUBE or WITH ROLLUP
27772        if trailing_cube {
27773            self.write_space();
27774            self.write_keyword("WITH CUBE");
27775        } else if trailing_rollup {
27776            self.write_space();
27777            self.write_keyword("WITH ROLLUP");
27778        }
27779
27780        // ClickHouse: WITH TOTALS
27781        if e.totals {
27782            self.write_space();
27783            self.write_keyword("WITH TOTALS");
27784        }
27785
27786        Ok(())
27787    }
27788
27789    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27790        // GROUPING(col1, col2, ...)
27791        self.write_keyword("GROUPING");
27792        self.write("(");
27793        for (i, expr) in e.expressions.iter().enumerate() {
27794            if i > 0 {
27795                self.write(", ");
27796            }
27797            self.generate_expression(expr)?;
27798        }
27799        self.write(")");
27800        Ok(())
27801    }
27802
27803    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27804        // GROUPING_ID(col1, col2, ...)
27805        self.write_keyword("GROUPING_ID");
27806        self.write("(");
27807        for (i, expr) in e.expressions.iter().enumerate() {
27808            if i > 0 {
27809                self.write(", ");
27810            }
27811            self.generate_expression(expr)?;
27812        }
27813        self.write(")");
27814        Ok(())
27815    }
27816
27817    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27818        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27819        self.write_keyword("GROUPING SETS");
27820        self.write(" (");
27821        for (i, expr) in e.expressions.iter().enumerate() {
27822            if i > 0 {
27823                self.write(", ");
27824            }
27825            self.generate_expression(expr)?;
27826        }
27827        self.write(")");
27828        Ok(())
27829    }
27830
27831    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27832        // HASH_AGG(this, expressions...)
27833        self.write_keyword("HASH_AGG");
27834        self.write("(");
27835        self.generate_expression(&e.this)?;
27836        for expr in &e.expressions {
27837            self.write(", ");
27838            self.generate_expression(expr)?;
27839        }
27840        self.write(")");
27841        Ok(())
27842    }
27843
27844    fn generate_having(&mut self, e: &Having) -> Result<()> {
27845        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27846        self.write_keyword("HAVING");
27847        self.write_space();
27848        self.generate_expression(&e.this)?;
27849        Ok(())
27850    }
27851
27852    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27853        // Python: this HAVING MAX|MIN expression
27854        self.generate_expression(&e.this)?;
27855        self.write_space();
27856        self.write_keyword("HAVING");
27857        self.write_space();
27858        if e.max.is_some() {
27859            self.write_keyword("MAX");
27860        } else {
27861            self.write_keyword("MIN");
27862        }
27863        self.write_space();
27864        self.generate_expression(&e.expression)?;
27865        Ok(())
27866    }
27867
27868    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27869        use crate::dialects::DialectType;
27870        // DuckDB: convert dollar-tagged strings to single-quoted
27871        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27872            // Extract the string content and output as single-quoted
27873            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27874                return self.generate_string_literal(s);
27875            }
27876        }
27877        // PostgreSQL: preserve dollar-quoting
27878        if matches!(
27879            self.config.dialect,
27880            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27881        ) {
27882            self.write("$");
27883            if let Some(tag) = &e.tag {
27884                self.generate_expression(tag)?;
27885            }
27886            self.write("$");
27887            self.generate_expression(&e.this)?;
27888            self.write("$");
27889            if let Some(tag) = &e.tag {
27890                self.generate_expression(tag)?;
27891            }
27892            self.write("$");
27893            return Ok(());
27894        }
27895        // Default: output as dollar-tagged
27896        self.write("$");
27897        if let Some(tag) = &e.tag {
27898            self.generate_expression(tag)?;
27899        }
27900        self.write("$");
27901        self.generate_expression(&e.this)?;
27902        self.write("$");
27903        if let Some(tag) = &e.tag {
27904            self.generate_expression(tag)?;
27905        }
27906        self.write("$");
27907        Ok(())
27908    }
27909
27910    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27911        // HEX_ENCODE(this)
27912        self.write_keyword("HEX_ENCODE");
27913        self.write("(");
27914        self.generate_expression(&e.this)?;
27915        self.write(")");
27916        Ok(())
27917    }
27918
27919    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27920        // Python: this (kind => expression)
27921        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27922        match e.this.as_ref() {
27923            Expression::Identifier(id) => self.write(&id.name),
27924            other => self.generate_expression(other)?,
27925        }
27926        self.write(" (");
27927        self.write(&e.kind);
27928        self.write(" => ");
27929        self.generate_expression(&e.expression)?;
27930        self.write(")");
27931        Ok(())
27932    }
27933
27934    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27935        // HLL(this, expressions...)
27936        self.write_keyword("HLL");
27937        self.write("(");
27938        self.generate_expression(&e.this)?;
27939        for expr in &e.expressions {
27940            self.write(", ");
27941            self.generate_expression(expr)?;
27942        }
27943        self.write(")");
27944        Ok(())
27945    }
27946
27947    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27948        // Python: IN|OUT|IN OUT
27949        if e.input_.is_some() && e.output.is_some() {
27950            self.write_keyword("IN OUT");
27951        } else if e.input_.is_some() {
27952            self.write_keyword("IN");
27953        } else if e.output.is_some() {
27954            self.write_keyword("OUT");
27955        }
27956        Ok(())
27957    }
27958
27959    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27960        // Python: INCLUDE this [column_def] [AS alias]
27961        self.write_keyword("INCLUDE");
27962        self.write_space();
27963        self.generate_expression(&e.this)?;
27964        if let Some(column_def) = &e.column_def {
27965            self.write_space();
27966            self.generate_expression(column_def)?;
27967        }
27968        if let Some(alias) = &e.alias {
27969            self.write_space();
27970            self.write_keyword("AS");
27971            self.write_space();
27972            self.write(alias);
27973        }
27974        Ok(())
27975    }
27976
27977    fn generate_index(&mut self, e: &Index) -> Result<()> {
27978        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27979        if e.unique {
27980            self.write_keyword("UNIQUE");
27981            self.write_space();
27982        }
27983        if e.primary.is_some() {
27984            self.write_keyword("PRIMARY");
27985            self.write_space();
27986        }
27987        if e.amp.is_some() {
27988            self.write_keyword("AMP");
27989            self.write_space();
27990        }
27991        if e.table.is_none() {
27992            self.write_keyword("INDEX");
27993            self.write_space();
27994        }
27995        if let Some(name) = &e.this {
27996            self.generate_expression(name)?;
27997            self.write_space();
27998        }
27999        if let Some(table) = &e.table {
28000            self.write_keyword("ON");
28001            self.write_space();
28002            self.generate_expression(table)?;
28003        }
28004        if !e.params.is_empty() {
28005            self.write("(");
28006            for (i, param) in e.params.iter().enumerate() {
28007                if i > 0 {
28008                    self.write(", ");
28009                }
28010                self.generate_expression(param)?;
28011            }
28012            self.write(")");
28013        }
28014        Ok(())
28015    }
28016
28017    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
28018        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
28019        if let Some(kind) = &e.kind {
28020            self.write(kind);
28021            self.write_space();
28022        }
28023        self.write_keyword("INDEX");
28024        if let Some(this) = &e.this {
28025            self.write_space();
28026            self.generate_expression(this)?;
28027        }
28028        if let Some(index_type) = &e.index_type {
28029            self.write_space();
28030            self.write_keyword("USING");
28031            self.write_space();
28032            self.generate_expression(index_type)?;
28033        }
28034        if !e.expressions.is_empty() {
28035            self.write(" (");
28036            for (i, expr) in e.expressions.iter().enumerate() {
28037                if i > 0 {
28038                    self.write(", ");
28039                }
28040                self.generate_expression(expr)?;
28041            }
28042            self.write(")");
28043        }
28044        for opt in &e.options {
28045            self.write_space();
28046            self.generate_expression(opt)?;
28047        }
28048        Ok(())
28049    }
28050
28051    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
28052        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
28053        if let Some(key_block_size) = &e.key_block_size {
28054            self.write_keyword("KEY_BLOCK_SIZE");
28055            self.write(" = ");
28056            self.generate_expression(key_block_size)?;
28057        } else if let Some(using) = &e.using {
28058            self.write_keyword("USING");
28059            self.write_space();
28060            self.generate_expression(using)?;
28061        } else if let Some(parser) = &e.parser {
28062            self.write_keyword("WITH PARSER");
28063            self.write_space();
28064            self.generate_expression(parser)?;
28065        } else if let Some(comment) = &e.comment {
28066            self.write_keyword("COMMENT");
28067            self.write_space();
28068            self.generate_expression(comment)?;
28069        } else if let Some(visible) = &e.visible {
28070            self.generate_expression(visible)?;
28071        } else if let Some(engine_attr) = &e.engine_attr {
28072            self.write_keyword("ENGINE_ATTRIBUTE");
28073            self.write(" = ");
28074            self.generate_expression(engine_attr)?;
28075        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
28076            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
28077            self.write(" = ");
28078            self.generate_expression(secondary_engine_attr)?;
28079        }
28080        Ok(())
28081    }
28082
28083    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28084        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28085        if let Some(using) = &e.using {
28086            self.write_keyword("USING");
28087            self.write_space();
28088            self.generate_expression(using)?;
28089        }
28090        if !e.columns.is_empty() {
28091            self.write("(");
28092            for (i, col) in e.columns.iter().enumerate() {
28093                if i > 0 {
28094                    self.write(", ");
28095                }
28096                self.generate_expression(col)?;
28097            }
28098            self.write(")");
28099        }
28100        if let Some(partition_by) = &e.partition_by {
28101            self.write_space();
28102            self.write_keyword("PARTITION BY");
28103            self.write_space();
28104            self.generate_expression(partition_by)?;
28105        }
28106        if let Some(where_) = &e.where_ {
28107            self.write_space();
28108            self.generate_expression(where_)?;
28109        }
28110        if let Some(include) = &e.include {
28111            self.write_space();
28112            self.write_keyword("INCLUDE");
28113            self.write(" (");
28114            self.generate_expression(include)?;
28115            self.write(")");
28116        }
28117        if let Some(with_storage) = &e.with_storage {
28118            self.write_space();
28119            self.write_keyword("WITH");
28120            self.write(" (");
28121            self.generate_expression(with_storage)?;
28122            self.write(")");
28123        }
28124        if let Some(tablespace) = &e.tablespace {
28125            self.write_space();
28126            self.write_keyword("USING INDEX TABLESPACE");
28127            self.write_space();
28128            self.generate_expression(tablespace)?;
28129        }
28130        Ok(())
28131    }
28132
28133    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28134        // Python: this INDEX [FOR target] (expressions)
28135        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28136        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28137        if let Expression::Identifier(id) = &*e.this {
28138            self.write_keyword(&id.name);
28139        } else {
28140            self.generate_expression(&e.this)?;
28141        }
28142        self.write_space();
28143        self.write_keyword("INDEX");
28144        if let Some(target) = &e.target {
28145            self.write_space();
28146            self.write_keyword("FOR");
28147            self.write_space();
28148            if let Expression::Identifier(id) = &**target {
28149                self.write_keyword(&id.name);
28150            } else {
28151                self.generate_expression(target)?;
28152            }
28153        }
28154        // Always output parentheses (even if empty, e.g. USE INDEX ())
28155        self.write(" (");
28156        for (i, expr) in e.expressions.iter().enumerate() {
28157            if i > 0 {
28158                self.write(", ");
28159            }
28160            self.generate_expression(expr)?;
28161        }
28162        self.write(")");
28163        Ok(())
28164    }
28165
28166    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28167        // INHERITS (table1, table2, ...)
28168        self.write_keyword("INHERITS");
28169        self.write(" (");
28170        for (i, expr) in e.expressions.iter().enumerate() {
28171            if i > 0 {
28172                self.write(", ");
28173            }
28174            self.generate_expression(expr)?;
28175        }
28176        self.write(")");
28177        Ok(())
28178    }
28179
28180    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28181        // INPUT(model)
28182        self.write_keyword("INPUT");
28183        self.write("(");
28184        self.generate_expression(&e.this)?;
28185        self.write(")");
28186        Ok(())
28187    }
28188
28189    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28190        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28191        if let Some(input_format) = &e.input_format {
28192            self.write_keyword("INPUTFORMAT");
28193            self.write_space();
28194            self.generate_expression(input_format)?;
28195        }
28196        if let Some(output_format) = &e.output_format {
28197            if e.input_format.is_some() {
28198                self.write(" ");
28199            }
28200            self.write_keyword("OUTPUTFORMAT");
28201            self.write_space();
28202            self.generate_expression(output_format)?;
28203        }
28204        Ok(())
28205    }
28206
28207    fn generate_install(&mut self, e: &Install) -> Result<()> {
28208        // [FORCE] INSTALL extension [FROM source]
28209        if e.force.is_some() {
28210            self.write_keyword("FORCE");
28211            self.write_space();
28212        }
28213        self.write_keyword("INSTALL");
28214        self.write_space();
28215        self.generate_expression(&e.this)?;
28216        if let Some(from) = &e.from_ {
28217            self.write_space();
28218            self.write_keyword("FROM");
28219            self.write_space();
28220            self.generate_expression(from)?;
28221        }
28222        Ok(())
28223    }
28224
28225    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28226        // INTERVAL 'expression' unit
28227        self.write_keyword("INTERVAL");
28228        self.write_space();
28229        // When a unit is specified and the expression is a number,
28230        self.generate_expression(&e.expression)?;
28231        if let Some(unit) = &e.unit {
28232            self.write_space();
28233            self.write(unit);
28234        }
28235        Ok(())
28236    }
28237
28238    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28239        // unit TO unit (e.g., HOUR TO SECOND)
28240        self.write(&format!("{:?}", e.this).to_uppercase());
28241        self.write_space();
28242        self.write_keyword("TO");
28243        self.write_space();
28244        self.write(&format!("{:?}", e.expression).to_uppercase());
28245        Ok(())
28246    }
28247
28248    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28249        // INTO [TEMPORARY|UNLOGGED] table
28250        self.write_keyword("INTO");
28251        if e.temporary {
28252            self.write_keyword(" TEMPORARY");
28253        }
28254        if e.unlogged.is_some() {
28255            self.write_keyword(" UNLOGGED");
28256        }
28257        if let Some(this) = &e.this {
28258            self.write_space();
28259            self.generate_expression(this)?;
28260        }
28261        if !e.expressions.is_empty() {
28262            self.write(" (");
28263            for (i, expr) in e.expressions.iter().enumerate() {
28264                if i > 0 {
28265                    self.write(", ");
28266                }
28267                self.generate_expression(expr)?;
28268            }
28269            self.write(")");
28270        }
28271        Ok(())
28272    }
28273
28274    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28275        // Python: this expression (e.g., _utf8 'string')
28276        self.generate_expression(&e.this)?;
28277        self.write_space();
28278        self.generate_expression(&e.expression)?;
28279        Ok(())
28280    }
28281
28282    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28283        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28284        self.write_keyword("WITH");
28285        if e.no.is_some() {
28286            self.write_keyword(" NO");
28287        }
28288        if e.concurrent.is_some() {
28289            self.write_keyword(" CONCURRENT");
28290        }
28291        self.write_keyword(" ISOLATED LOADING");
28292        if let Some(target) = &e.target {
28293            self.write_space();
28294            self.generate_expression(target)?;
28295        }
28296        Ok(())
28297    }
28298
28299    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28300        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28301        self.write_keyword("JSON");
28302        if let Some(this) = &e.this {
28303            self.write_space();
28304            self.generate_expression(this)?;
28305        }
28306        if let Some(with_) = &e.with_ {
28307            // Check if it's a truthy boolean
28308            if let Expression::Boolean(b) = with_.as_ref() {
28309                if b.value {
28310                    self.write_keyword(" WITH");
28311                } else {
28312                    self.write_keyword(" WITHOUT");
28313                }
28314            }
28315        }
28316        if e.unique {
28317            self.write_keyword(" UNIQUE KEYS");
28318        }
28319        Ok(())
28320    }
28321
28322    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28323        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28324        self.write_keyword("JSON_ARRAY");
28325        self.write("(");
28326        for (i, expr) in e.expressions.iter().enumerate() {
28327            if i > 0 {
28328                self.write(", ");
28329            }
28330            self.generate_expression(expr)?;
28331        }
28332        if let Some(null_handling) = &e.null_handling {
28333            self.write_space();
28334            self.generate_expression(null_handling)?;
28335        }
28336        if let Some(return_type) = &e.return_type {
28337            self.write_space();
28338            self.write_keyword("RETURNING");
28339            self.write_space();
28340            self.generate_expression(return_type)?;
28341        }
28342        if e.strict.is_some() {
28343            self.write_space();
28344            self.write_keyword("STRICT");
28345        }
28346        self.write(")");
28347        Ok(())
28348    }
28349
28350    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28351        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28352        self.write_keyword("JSON_ARRAYAGG");
28353        self.write("(");
28354        self.generate_expression(&e.this)?;
28355        if let Some(order) = &e.order {
28356            self.write_space();
28357            // Order is stored as an OrderBy expression
28358            if let Expression::OrderBy(ob) = order.as_ref() {
28359                self.write_keyword("ORDER BY");
28360                self.write_space();
28361                for (i, ord) in ob.expressions.iter().enumerate() {
28362                    if i > 0 {
28363                        self.write(", ");
28364                    }
28365                    self.generate_ordered(ord)?;
28366                }
28367            } else {
28368                // Fallback: generate the expression directly
28369                self.generate_expression(order)?;
28370            }
28371        }
28372        if let Some(null_handling) = &e.null_handling {
28373            self.write_space();
28374            self.generate_expression(null_handling)?;
28375        }
28376        if let Some(return_type) = &e.return_type {
28377            self.write_space();
28378            self.write_keyword("RETURNING");
28379            self.write_space();
28380            self.generate_expression(return_type)?;
28381        }
28382        if e.strict.is_some() {
28383            self.write_space();
28384            self.write_keyword("STRICT");
28385        }
28386        self.write(")");
28387        Ok(())
28388    }
28389
28390    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28391        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28392        self.write_keyword("JSON_OBJECTAGG");
28393        self.write("(");
28394        for (i, expr) in e.expressions.iter().enumerate() {
28395            if i > 0 {
28396                self.write(", ");
28397            }
28398            self.generate_expression(expr)?;
28399        }
28400        if let Some(null_handling) = &e.null_handling {
28401            self.write_space();
28402            self.generate_expression(null_handling)?;
28403        }
28404        if let Some(unique_keys) = &e.unique_keys {
28405            self.write_space();
28406            if let Expression::Boolean(b) = unique_keys.as_ref() {
28407                if b.value {
28408                    self.write_keyword("WITH UNIQUE KEYS");
28409                } else {
28410                    self.write_keyword("WITHOUT UNIQUE KEYS");
28411                }
28412            }
28413        }
28414        if let Some(return_type) = &e.return_type {
28415            self.write_space();
28416            self.write_keyword("RETURNING");
28417            self.write_space();
28418            self.generate_expression(return_type)?;
28419        }
28420        self.write(")");
28421        Ok(())
28422    }
28423
28424    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28425        // JSON_ARRAY_APPEND(this, path, value, ...)
28426        self.write_keyword("JSON_ARRAY_APPEND");
28427        self.write("(");
28428        self.generate_expression(&e.this)?;
28429        for expr in &e.expressions {
28430            self.write(", ");
28431            self.generate_expression(expr)?;
28432        }
28433        self.write(")");
28434        Ok(())
28435    }
28436
28437    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28438        // JSON_ARRAY_CONTAINS(this, expression)
28439        self.write_keyword("JSON_ARRAY_CONTAINS");
28440        self.write("(");
28441        self.generate_expression(&e.this)?;
28442        self.write(", ");
28443        self.generate_expression(&e.expression)?;
28444        self.write(")");
28445        Ok(())
28446    }
28447
28448    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28449        // JSON_ARRAY_INSERT(this, path, value, ...)
28450        self.write_keyword("JSON_ARRAY_INSERT");
28451        self.write("(");
28452        self.generate_expression(&e.this)?;
28453        for expr in &e.expressions {
28454            self.write(", ");
28455            self.generate_expression(expr)?;
28456        }
28457        self.write(")");
28458        Ok(())
28459    }
28460
28461    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28462        // JSONB_EXISTS(this, path)
28463        self.write_keyword("JSONB_EXISTS");
28464        self.write("(");
28465        self.generate_expression(&e.this)?;
28466        if let Some(path) = &e.path {
28467            self.write(", ");
28468            self.generate_expression(path)?;
28469        }
28470        self.write(")");
28471        Ok(())
28472    }
28473
28474    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28475        // JSONB_EXTRACT_SCALAR(this, expression)
28476        self.write_keyword("JSONB_EXTRACT_SCALAR");
28477        self.write("(");
28478        self.generate_expression(&e.this)?;
28479        self.write(", ");
28480        self.generate_expression(&e.expression)?;
28481        self.write(")");
28482        Ok(())
28483    }
28484
28485    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28486        // JSONB_OBJECT_AGG(this, expression)
28487        self.write_keyword("JSONB_OBJECT_AGG");
28488        self.write("(");
28489        self.generate_expression(&e.this)?;
28490        self.write(", ");
28491        self.generate_expression(&e.expression)?;
28492        self.write(")");
28493        Ok(())
28494    }
28495
28496    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28497        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28498        if let Some(nested_schema) = &e.nested_schema {
28499            self.write_keyword("NESTED");
28500            if let Some(path) = &e.path {
28501                self.write_space();
28502                self.write_keyword("PATH");
28503                self.write_space();
28504                self.generate_expression(path)?;
28505            }
28506            self.write_space();
28507            self.generate_expression(nested_schema)?;
28508        } else {
28509            if let Some(this) = &e.this {
28510                self.generate_expression(this)?;
28511            }
28512            if let Some(kind) = &e.kind {
28513                self.write_space();
28514                self.write(kind);
28515            }
28516            if let Some(path) = &e.path {
28517                self.write_space();
28518                self.write_keyword("PATH");
28519                self.write_space();
28520                self.generate_expression(path)?;
28521            }
28522            if e.ordinality.is_some() {
28523                self.write_keyword(" FOR ORDINALITY");
28524            }
28525        }
28526        Ok(())
28527    }
28528
28529    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28530        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28531        self.write_keyword("JSON_EXISTS");
28532        self.write("(");
28533        self.generate_expression(&e.this)?;
28534        if let Some(path) = &e.path {
28535            self.write(", ");
28536            self.generate_expression(path)?;
28537        }
28538        if let Some(passing) = &e.passing {
28539            self.write_space();
28540            self.write_keyword("PASSING");
28541            self.write_space();
28542            self.generate_expression(passing)?;
28543        }
28544        if let Some(on_condition) = &e.on_condition {
28545            self.write_space();
28546            self.generate_expression(on_condition)?;
28547        }
28548        self.write(")");
28549        Ok(())
28550    }
28551
28552    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28553        self.generate_expression(&e.this)?;
28554        self.write(".:");
28555        self.generate_data_type(&e.to)?;
28556        Ok(())
28557    }
28558
28559    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28560        // JSON_EXTRACT_ARRAY(this, expression)
28561        self.write_keyword("JSON_EXTRACT_ARRAY");
28562        self.write("(");
28563        self.generate_expression(&e.this)?;
28564        if let Some(expr) = &e.expression {
28565            self.write(", ");
28566            self.generate_expression(expr)?;
28567        }
28568        self.write(")");
28569        Ok(())
28570    }
28571
28572    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28573        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28574        if let Some(option) = &e.option {
28575            self.generate_expression(option)?;
28576            self.write_space();
28577        }
28578        self.write_keyword("QUOTES");
28579        if e.scalar.is_some() {
28580            self.write_keyword(" SCALAR_ONLY");
28581        }
28582        Ok(())
28583    }
28584
28585    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28586        // JSON_EXTRACT_SCALAR(this, expression)
28587        self.write_keyword("JSON_EXTRACT_SCALAR");
28588        self.write("(");
28589        self.generate_expression(&e.this)?;
28590        self.write(", ");
28591        self.generate_expression(&e.expression)?;
28592        self.write(")");
28593        Ok(())
28594    }
28595
28596    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28597        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28598        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28599        // Otherwise output JSON_EXTRACT(this, expression)
28600        if e.variant_extract.is_some() {
28601            use crate::dialects::DialectType;
28602            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28603                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28604                self.generate_expression(&e.this)?;
28605                self.write(":");
28606                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28607                // We need to output it without quotes
28608                match e.expression.as_ref() {
28609                    Expression::Literal(Literal::String(s)) => {
28610                        self.write(s);
28611                    }
28612                    _ => {
28613                        // Fallback: generate as-is (shouldn't happen in typical cases)
28614                        self.generate_expression(&e.expression)?;
28615                    }
28616                }
28617            } else {
28618                // Snowflake and others: use GET_PATH(col, 'path')
28619                self.write_keyword("GET_PATH");
28620                self.write("(");
28621                self.generate_expression(&e.this)?;
28622                self.write(", ");
28623                self.generate_expression(&e.expression)?;
28624                self.write(")");
28625            }
28626        } else {
28627            self.write_keyword("JSON_EXTRACT");
28628            self.write("(");
28629            self.generate_expression(&e.this)?;
28630            self.write(", ");
28631            self.generate_expression(&e.expression)?;
28632            for expr in &e.expressions {
28633                self.write(", ");
28634                self.generate_expression(expr)?;
28635            }
28636            self.write(")");
28637        }
28638        Ok(())
28639    }
28640
28641    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28642        // Output: {expr} FORMAT JSON
28643        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28644        if let Some(this) = &e.this {
28645            self.generate_expression(this)?;
28646            self.write_space();
28647        }
28648        self.write_keyword("FORMAT JSON");
28649        Ok(())
28650    }
28651
28652    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28653        // key: value (for JSON objects)
28654        self.generate_expression(&e.this)?;
28655        self.write(": ");
28656        self.generate_expression(&e.expression)?;
28657        Ok(())
28658    }
28659
28660    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28661        // JSON_KEYS(this, expression, expressions...)
28662        self.write_keyword("JSON_KEYS");
28663        self.write("(");
28664        self.generate_expression(&e.this)?;
28665        if let Some(expr) = &e.expression {
28666            self.write(", ");
28667            self.generate_expression(expr)?;
28668        }
28669        for expr in &e.expressions {
28670            self.write(", ");
28671            self.generate_expression(expr)?;
28672        }
28673        self.write(")");
28674        Ok(())
28675    }
28676
28677    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28678        // JSON_KEYS(this, expression)
28679        self.write_keyword("JSON_KEYS");
28680        self.write("(");
28681        self.generate_expression(&e.this)?;
28682        if let Some(expr) = &e.expression {
28683            self.write(", ");
28684            self.generate_expression(expr)?;
28685        }
28686        self.write(")");
28687        Ok(())
28688    }
28689
28690    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28691        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28692        // The path components are concatenated without spaces
28693        let mut path_str = String::new();
28694        for expr in &e.expressions {
28695            match expr {
28696                Expression::JSONPathRoot(_) => {
28697                    path_str.push('$');
28698                }
28699                Expression::JSONPathKey(k) => {
28700                    // .key or ."key" (quote if key has special characters)
28701                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28702                        k.this.as_ref()
28703                    {
28704                        path_str.push('.');
28705                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28706                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28707                        if needs_quoting {
28708                            path_str.push('"');
28709                            path_str.push_str(s);
28710                            path_str.push('"');
28711                        } else {
28712                            path_str.push_str(s);
28713                        }
28714                    }
28715                }
28716                Expression::JSONPathSubscript(s) => {
28717                    // [index]
28718                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28719                        s.this.as_ref()
28720                    {
28721                        path_str.push('[');
28722                        path_str.push_str(n);
28723                        path_str.push(']');
28724                    }
28725                }
28726                _ => {
28727                    // For other path parts, try to generate them
28728                    let mut temp_gen = Self::with_config(self.config.clone());
28729                    temp_gen.generate_expression(expr)?;
28730                    path_str.push_str(&temp_gen.output);
28731                }
28732            }
28733        }
28734        // Output as quoted string
28735        self.write("'");
28736        self.write(&path_str);
28737        self.write("'");
28738        Ok(())
28739    }
28740
28741    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28742        // JSON path filter: ?(predicate)
28743        self.write("?(");
28744        self.generate_expression(&e.this)?;
28745        self.write(")");
28746        Ok(())
28747    }
28748
28749    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28750        // JSON path key: .key or ["key"]
28751        self.write(".");
28752        self.generate_expression(&e.this)?;
28753        Ok(())
28754    }
28755
28756    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28757        // JSON path recursive descent: ..
28758        self.write("..");
28759        if let Some(this) = &e.this {
28760            self.generate_expression(this)?;
28761        }
28762        Ok(())
28763    }
28764
28765    fn generate_json_path_root(&mut self) -> Result<()> {
28766        // JSON path root: $
28767        self.write("$");
28768        Ok(())
28769    }
28770
28771    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28772        // JSON path script: (expression)
28773        self.write("(");
28774        self.generate_expression(&e.this)?;
28775        self.write(")");
28776        Ok(())
28777    }
28778
28779    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28780        // JSON path selector: *
28781        self.generate_expression(&e.this)?;
28782        Ok(())
28783    }
28784
28785    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28786        // JSON path slice: [start:end:step]
28787        self.write("[");
28788        if let Some(start) = &e.start {
28789            self.generate_expression(start)?;
28790        }
28791        self.write(":");
28792        if let Some(end) = &e.end {
28793            self.generate_expression(end)?;
28794        }
28795        if let Some(step) = &e.step {
28796            self.write(":");
28797            self.generate_expression(step)?;
28798        }
28799        self.write("]");
28800        Ok(())
28801    }
28802
28803    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28804        // JSON path subscript: [index] or [*]
28805        self.write("[");
28806        self.generate_expression(&e.this)?;
28807        self.write("]");
28808        Ok(())
28809    }
28810
28811    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28812        // JSON path union: [key1, key2, ...]
28813        self.write("[");
28814        for (i, expr) in e.expressions.iter().enumerate() {
28815            if i > 0 {
28816                self.write(", ");
28817            }
28818            self.generate_expression(expr)?;
28819        }
28820        self.write("]");
28821        Ok(())
28822    }
28823
28824    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28825        // JSON_REMOVE(this, path1, path2, ...)
28826        self.write_keyword("JSON_REMOVE");
28827        self.write("(");
28828        self.generate_expression(&e.this)?;
28829        for expr in &e.expressions {
28830            self.write(", ");
28831            self.generate_expression(expr)?;
28832        }
28833        self.write(")");
28834        Ok(())
28835    }
28836
28837    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28838        // COLUMNS(col1 type, col2 type, ...)
28839        // When pretty printing and content is too wide, format with each column on a separate line
28840        self.write_keyword("COLUMNS");
28841        self.write("(");
28842
28843        if self.config.pretty && !e.expressions.is_empty() {
28844            // First, generate all expressions into strings to check width
28845            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28846            for expr in &e.expressions {
28847                let mut temp_gen = Generator::with_config(self.config.clone());
28848                temp_gen.generate_expression(expr)?;
28849                expr_strings.push(temp_gen.output);
28850            }
28851
28852            // Check if total width exceeds max_text_width
28853            if self.too_wide(&expr_strings) {
28854                // Pretty print: each column on its own line
28855                self.write_newline();
28856                self.indent_level += 1;
28857                for (i, expr_str) in expr_strings.iter().enumerate() {
28858                    if i > 0 {
28859                        self.write(",");
28860                        self.write_newline();
28861                    }
28862                    self.write_indent();
28863                    self.write(expr_str);
28864                }
28865                self.write_newline();
28866                self.indent_level -= 1;
28867                self.write_indent();
28868            } else {
28869                // Compact: all on one line
28870                for (i, expr_str) in expr_strings.iter().enumerate() {
28871                    if i > 0 {
28872                        self.write(", ");
28873                    }
28874                    self.write(expr_str);
28875                }
28876            }
28877        } else {
28878            // Non-pretty mode: compact format
28879            for (i, expr) in e.expressions.iter().enumerate() {
28880                if i > 0 {
28881                    self.write(", ");
28882                }
28883                self.generate_expression(expr)?;
28884            }
28885        }
28886        self.write(")");
28887        Ok(())
28888    }
28889
28890    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28891        // JSON_SET(this, path, value, ...)
28892        self.write_keyword("JSON_SET");
28893        self.write("(");
28894        self.generate_expression(&e.this)?;
28895        for expr in &e.expressions {
28896            self.write(", ");
28897            self.generate_expression(expr)?;
28898        }
28899        self.write(")");
28900        Ok(())
28901    }
28902
28903    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28904        // JSON_STRIP_NULLS(this, expression)
28905        self.write_keyword("JSON_STRIP_NULLS");
28906        self.write("(");
28907        self.generate_expression(&e.this)?;
28908        if let Some(expr) = &e.expression {
28909            self.write(", ");
28910            self.generate_expression(expr)?;
28911        }
28912        self.write(")");
28913        Ok(())
28914    }
28915
28916    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28917        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28918        self.write_keyword("JSON_TABLE");
28919        self.write("(");
28920        self.generate_expression(&e.this)?;
28921        if let Some(path) = &e.path {
28922            self.write(", ");
28923            self.generate_expression(path)?;
28924        }
28925        if let Some(error_handling) = &e.error_handling {
28926            self.write_space();
28927            self.generate_expression(error_handling)?;
28928        }
28929        if let Some(empty_handling) = &e.empty_handling {
28930            self.write_space();
28931            self.generate_expression(empty_handling)?;
28932        }
28933        if let Some(schema) = &e.schema {
28934            self.write_space();
28935            self.generate_expression(schema)?;
28936        }
28937        self.write(")");
28938        Ok(())
28939    }
28940
28941    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28942        // JSON_TYPE(this)
28943        self.write_keyword("JSON_TYPE");
28944        self.write("(");
28945        self.generate_expression(&e.this)?;
28946        self.write(")");
28947        Ok(())
28948    }
28949
28950    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28951        // JSON_VALUE(this, path RETURNING type ON condition)
28952        self.write_keyword("JSON_VALUE");
28953        self.write("(");
28954        self.generate_expression(&e.this)?;
28955        if let Some(path) = &e.path {
28956            self.write(", ");
28957            self.generate_expression(path)?;
28958        }
28959        if let Some(returning) = &e.returning {
28960            self.write_space();
28961            self.write_keyword("RETURNING");
28962            self.write_space();
28963            self.generate_expression(returning)?;
28964        }
28965        if let Some(on_condition) = &e.on_condition {
28966            self.write_space();
28967            self.generate_expression(on_condition)?;
28968        }
28969        self.write(")");
28970        Ok(())
28971    }
28972
28973    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28974        // JSON_VALUE_ARRAY(this)
28975        self.write_keyword("JSON_VALUE_ARRAY");
28976        self.write("(");
28977        self.generate_expression(&e.this)?;
28978        self.write(")");
28979        Ok(())
28980    }
28981
28982    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28983        // JAROWINKLER_SIMILARITY(str1, str2)
28984        self.write_keyword("JAROWINKLER_SIMILARITY");
28985        self.write("(");
28986        self.generate_expression(&e.this)?;
28987        self.write(", ");
28988        self.generate_expression(&e.expression)?;
28989        self.write(")");
28990        Ok(())
28991    }
28992
28993    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28994        // Python: this(expressions)
28995        self.generate_expression(&e.this)?;
28996        self.write("(");
28997        for (i, expr) in e.expressions.iter().enumerate() {
28998            if i > 0 {
28999                self.write(", ");
29000            }
29001            self.generate_expression(expr)?;
29002        }
29003        self.write(")");
29004        Ok(())
29005    }
29006
29007    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
29008        // Python: {no}{local}{dual}{before}{after}JOURNAL
29009        if e.no.is_some() {
29010            self.write_keyword("NO ");
29011        }
29012        if let Some(local) = &e.local {
29013            self.generate_expression(local)?;
29014            self.write_space();
29015        }
29016        if e.dual.is_some() {
29017            self.write_keyword("DUAL ");
29018        }
29019        if e.before.is_some() {
29020            self.write_keyword("BEFORE ");
29021        }
29022        if e.after.is_some() {
29023            self.write_keyword("AFTER ");
29024        }
29025        self.write_keyword("JOURNAL");
29026        Ok(())
29027    }
29028
29029    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
29030        // LANGUAGE language_name
29031        self.write_keyword("LANGUAGE");
29032        self.write_space();
29033        self.generate_expression(&e.this)?;
29034        Ok(())
29035    }
29036
29037    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
29038        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
29039        if e.view.is_some() {
29040            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
29041            self.write_keyword("LATERAL VIEW");
29042            if e.outer.is_some() {
29043                self.write_space();
29044                self.write_keyword("OUTER");
29045            }
29046            self.write_space();
29047            self.generate_expression(&e.this)?;
29048            if let Some(alias) = &e.alias {
29049                self.write_space();
29050                self.write(alias);
29051            }
29052        } else {
29053            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
29054            self.write_keyword("LATERAL");
29055            self.write_space();
29056            self.generate_expression(&e.this)?;
29057            if e.ordinality.is_some() {
29058                self.write_space();
29059                self.write_keyword("WITH ORDINALITY");
29060            }
29061            if let Some(alias) = &e.alias {
29062                self.write_space();
29063                self.write_keyword("AS");
29064                self.write_space();
29065                self.write(alias);
29066                if !e.column_aliases.is_empty() {
29067                    self.write("(");
29068                    for (i, col) in e.column_aliases.iter().enumerate() {
29069                        if i > 0 {
29070                            self.write(", ");
29071                        }
29072                        self.write(col);
29073                    }
29074                    self.write(")");
29075                }
29076            }
29077        }
29078        Ok(())
29079    }
29080
29081    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29082        // Python: LIKE this [options]
29083        self.write_keyword("LIKE");
29084        self.write_space();
29085        self.generate_expression(&e.this)?;
29086        for expr in &e.expressions {
29087            self.write_space();
29088            self.generate_expression(expr)?;
29089        }
29090        Ok(())
29091    }
29092
29093    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29094        self.write_keyword("LIMIT");
29095        self.write_space();
29096        self.write_limit_expr(&e.this)?;
29097        if e.percent {
29098            self.write_space();
29099            self.write_keyword("PERCENT");
29100        }
29101        // Emit any comments that were captured from before the LIMIT keyword
29102        for comment in &e.comments {
29103            self.write(" ");
29104            self.write_formatted_comment(comment);
29105        }
29106        Ok(())
29107    }
29108
29109    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29110        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29111        if e.percent.is_some() {
29112            self.write_keyword(" PERCENT");
29113        }
29114        if e.rows.is_some() {
29115            self.write_keyword(" ROWS");
29116        }
29117        if e.with_ties.is_some() {
29118            self.write_keyword(" WITH TIES");
29119        } else if e.rows.is_some() {
29120            self.write_keyword(" ONLY");
29121        }
29122        Ok(())
29123    }
29124
29125    fn generate_list(&mut self, e: &List) -> Result<()> {
29126        use crate::dialects::DialectType;
29127        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29128
29129        // Check if this is a subquery-based list (LIST(SELECT ...))
29130        if e.expressions.len() == 1 {
29131            if let Expression::Select(_) = &e.expressions[0] {
29132                self.write_keyword("LIST");
29133                self.write("(");
29134                self.generate_expression(&e.expressions[0])?;
29135                self.write(")");
29136                return Ok(());
29137            }
29138        }
29139
29140        // For Materialize, output as LIST[expr, expr, ...]
29141        if is_materialize {
29142            self.write_keyword("LIST");
29143            self.write("[");
29144            for (i, expr) in e.expressions.iter().enumerate() {
29145                if i > 0 {
29146                    self.write(", ");
29147                }
29148                self.generate_expression(expr)?;
29149            }
29150            self.write("]");
29151        } else {
29152            // For other dialects, output as LIST(expr, expr, ...)
29153            self.write_keyword("LIST");
29154            self.write("(");
29155            for (i, expr) in e.expressions.iter().enumerate() {
29156                if i > 0 {
29157                    self.write(", ");
29158                }
29159                self.generate_expression(expr)?;
29160            }
29161            self.write(")");
29162        }
29163        Ok(())
29164    }
29165
29166    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29167        // Check if this is a subquery-based map (MAP(SELECT ...))
29168        if let Expression::Select(_) = &*e.this {
29169            self.write_keyword("MAP");
29170            self.write("(");
29171            self.generate_expression(&e.this)?;
29172            self.write(")");
29173            return Ok(());
29174        }
29175
29176        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29177
29178        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29179        self.write_keyword("MAP");
29180        if is_duckdb {
29181            self.write(" {");
29182        } else {
29183            self.write("[");
29184        }
29185        if let Expression::Struct(s) = &*e.this {
29186            for (i, (_, expr)) in s.fields.iter().enumerate() {
29187                if i > 0 {
29188                    self.write(", ");
29189                }
29190                if let Expression::PropertyEQ(op) = expr {
29191                    self.generate_expression(&op.left)?;
29192                    if is_duckdb {
29193                        self.write(": ");
29194                    } else {
29195                        self.write(" => ");
29196                    }
29197                    self.generate_expression(&op.right)?;
29198                } else {
29199                    self.generate_expression(expr)?;
29200                }
29201            }
29202        }
29203        if is_duckdb {
29204            self.write("}");
29205        } else {
29206            self.write("]");
29207        }
29208        Ok(())
29209    }
29210
29211    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29212        // Python: LOCALTIME or LOCALTIME(precision)
29213        self.write_keyword("LOCALTIME");
29214        if let Some(precision) = &e.this {
29215            self.write("(");
29216            self.generate_expression(precision)?;
29217            self.write(")");
29218        }
29219        Ok(())
29220    }
29221
29222    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29223        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29224        self.write_keyword("LOCALTIMESTAMP");
29225        if let Some(precision) = &e.this {
29226            self.write("(");
29227            self.generate_expression(precision)?;
29228            self.write(")");
29229        }
29230        Ok(())
29231    }
29232
29233    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29234        // LOCATION 'path'
29235        self.write_keyword("LOCATION");
29236        self.write_space();
29237        self.generate_expression(&e.this)?;
29238        Ok(())
29239    }
29240
29241    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29242        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29243        if e.update.is_some() {
29244            if e.key.is_some() {
29245                self.write_keyword("FOR NO KEY UPDATE");
29246            } else {
29247                self.write_keyword("FOR UPDATE");
29248            }
29249        } else {
29250            if e.key.is_some() {
29251                self.write_keyword("FOR KEY SHARE");
29252            } else {
29253                self.write_keyword("FOR SHARE");
29254            }
29255        }
29256        if !e.expressions.is_empty() {
29257            self.write_keyword(" OF ");
29258            for (i, expr) in e.expressions.iter().enumerate() {
29259                if i > 0 {
29260                    self.write(", ");
29261                }
29262                self.generate_expression(expr)?;
29263            }
29264        }
29265        // Handle wait option following Python sqlglot convention:
29266        // - Boolean(true) -> NOWAIT
29267        // - Boolean(false) -> SKIP LOCKED
29268        // - Literal (number) -> WAIT n
29269        if let Some(wait) = &e.wait {
29270            match wait.as_ref() {
29271                Expression::Boolean(b) => {
29272                    if b.value {
29273                        self.write_keyword(" NOWAIT");
29274                    } else {
29275                        self.write_keyword(" SKIP LOCKED");
29276                    }
29277                }
29278                _ => {
29279                    // It's a literal (number), output WAIT n
29280                    self.write_keyword(" WAIT ");
29281                    self.generate_expression(wait)?;
29282                }
29283            }
29284        }
29285        Ok(())
29286    }
29287
29288    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29289        // LOCK property
29290        self.write_keyword("LOCK");
29291        self.write_space();
29292        self.generate_expression(&e.this)?;
29293        Ok(())
29294    }
29295
29296    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29297        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29298        self.write_keyword("LOCKING");
29299        self.write_space();
29300        self.write(&e.kind);
29301        if let Some(this) = &e.this {
29302            self.write_space();
29303            self.generate_expression(this)?;
29304        }
29305        if let Some(for_or_in) = &e.for_or_in {
29306            self.write_space();
29307            self.generate_expression(for_or_in)?;
29308        }
29309        if let Some(lock_type) = &e.lock_type {
29310            self.write_space();
29311            self.generate_expression(lock_type)?;
29312        }
29313        if e.override_.is_some() {
29314            self.write_keyword(" OVERRIDE");
29315        }
29316        Ok(())
29317    }
29318
29319    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29320        // this expression
29321        self.generate_expression(&e.this)?;
29322        self.write_space();
29323        self.generate_expression(&e.expression)?;
29324        Ok(())
29325    }
29326
29327    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29328        // [NO] LOG
29329        if e.no.is_some() {
29330            self.write_keyword("NO ");
29331        }
29332        self.write_keyword("LOG");
29333        Ok(())
29334    }
29335
29336    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29337        // MD5(this, expressions...)
29338        self.write_keyword("MD5");
29339        self.write("(");
29340        self.generate_expression(&e.this)?;
29341        for expr in &e.expressions {
29342            self.write(", ");
29343            self.generate_expression(expr)?;
29344        }
29345        self.write(")");
29346        Ok(())
29347    }
29348
29349    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29350        // ML.FORECAST(model, [params])
29351        self.write_keyword("ML.FORECAST");
29352        self.write("(");
29353        self.generate_expression(&e.this)?;
29354        if let Some(expression) = &e.expression {
29355            self.write(", ");
29356            self.generate_expression(expression)?;
29357        }
29358        if let Some(params) = &e.params_struct {
29359            self.write(", ");
29360            self.generate_expression(params)?;
29361        }
29362        self.write(")");
29363        Ok(())
29364    }
29365
29366    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29367        // ML.TRANSLATE(model, input, [params])
29368        self.write_keyword("ML.TRANSLATE");
29369        self.write("(");
29370        self.generate_expression(&e.this)?;
29371        self.write(", ");
29372        self.generate_expression(&e.expression)?;
29373        if let Some(params) = &e.params_struct {
29374            self.write(", ");
29375            self.generate_expression(params)?;
29376        }
29377        self.write(")");
29378        Ok(())
29379    }
29380
29381    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29382        // MAKE_INTERVAL(years => x, months => y, ...)
29383        self.write_keyword("MAKE_INTERVAL");
29384        self.write("(");
29385        let mut first = true;
29386        if let Some(year) = &e.year {
29387            self.write("years => ");
29388            self.generate_expression(year)?;
29389            first = false;
29390        }
29391        if let Some(month) = &e.month {
29392            if !first {
29393                self.write(", ");
29394            }
29395            self.write("months => ");
29396            self.generate_expression(month)?;
29397            first = false;
29398        }
29399        if let Some(week) = &e.week {
29400            if !first {
29401                self.write(", ");
29402            }
29403            self.write("weeks => ");
29404            self.generate_expression(week)?;
29405            first = false;
29406        }
29407        if let Some(day) = &e.day {
29408            if !first {
29409                self.write(", ");
29410            }
29411            self.write("days => ");
29412            self.generate_expression(day)?;
29413            first = false;
29414        }
29415        if let Some(hour) = &e.hour {
29416            if !first {
29417                self.write(", ");
29418            }
29419            self.write("hours => ");
29420            self.generate_expression(hour)?;
29421            first = false;
29422        }
29423        if let Some(minute) = &e.minute {
29424            if !first {
29425                self.write(", ");
29426            }
29427            self.write("mins => ");
29428            self.generate_expression(minute)?;
29429            first = false;
29430        }
29431        if let Some(second) = &e.second {
29432            if !first {
29433                self.write(", ");
29434            }
29435            self.write("secs => ");
29436            self.generate_expression(second)?;
29437        }
29438        self.write(")");
29439        Ok(())
29440    }
29441
29442    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29443        // MANHATTAN_DISTANCE(vector1, vector2)
29444        self.write_keyword("MANHATTAN_DISTANCE");
29445        self.write("(");
29446        self.generate_expression(&e.this)?;
29447        self.write(", ");
29448        self.generate_expression(&e.expression)?;
29449        self.write(")");
29450        Ok(())
29451    }
29452
29453    fn generate_map(&mut self, e: &Map) -> Result<()> {
29454        // MAP(key1, value1, key2, value2, ...)
29455        self.write_keyword("MAP");
29456        self.write("(");
29457        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29458            if i > 0 {
29459                self.write(", ");
29460            }
29461            self.generate_expression(key)?;
29462            self.write(", ");
29463            self.generate_expression(value)?;
29464        }
29465        self.write(")");
29466        Ok(())
29467    }
29468
29469    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29470        // MAP_CAT(map1, map2)
29471        self.write_keyword("MAP_CAT");
29472        self.write("(");
29473        self.generate_expression(&e.this)?;
29474        self.write(", ");
29475        self.generate_expression(&e.expression)?;
29476        self.write(")");
29477        Ok(())
29478    }
29479
29480    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29481        // MAP_DELETE(map, key1, key2, ...)
29482        self.write_keyword("MAP_DELETE");
29483        self.write("(");
29484        self.generate_expression(&e.this)?;
29485        for expr in &e.expressions {
29486            self.write(", ");
29487            self.generate_expression(expr)?;
29488        }
29489        self.write(")");
29490        Ok(())
29491    }
29492
29493    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29494        // MAP_INSERT(map, key, value, [update_flag])
29495        self.write_keyword("MAP_INSERT");
29496        self.write("(");
29497        self.generate_expression(&e.this)?;
29498        if let Some(key) = &e.key {
29499            self.write(", ");
29500            self.generate_expression(key)?;
29501        }
29502        if let Some(value) = &e.value {
29503            self.write(", ");
29504            self.generate_expression(value)?;
29505        }
29506        if let Some(update_flag) = &e.update_flag {
29507            self.write(", ");
29508            self.generate_expression(update_flag)?;
29509        }
29510        self.write(")");
29511        Ok(())
29512    }
29513
29514    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29515        // MAP_PICK(map, key1, key2, ...)
29516        self.write_keyword("MAP_PICK");
29517        self.write("(");
29518        self.generate_expression(&e.this)?;
29519        for expr in &e.expressions {
29520            self.write(", ");
29521            self.generate_expression(expr)?;
29522        }
29523        self.write(")");
29524        Ok(())
29525    }
29526
29527    fn generate_masking_policy_column_constraint(
29528        &mut self,
29529        e: &MaskingPolicyColumnConstraint,
29530    ) -> Result<()> {
29531        // Python: MASKING POLICY name [USING (cols)]
29532        self.write_keyword("MASKING POLICY");
29533        self.write_space();
29534        self.generate_expression(&e.this)?;
29535        if !e.expressions.is_empty() {
29536            self.write_keyword(" USING");
29537            self.write(" (");
29538            for (i, expr) in e.expressions.iter().enumerate() {
29539                if i > 0 {
29540                    self.write(", ");
29541                }
29542                self.generate_expression(expr)?;
29543            }
29544            self.write(")");
29545        }
29546        Ok(())
29547    }
29548
29549    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29550        if matches!(
29551            self.config.dialect,
29552            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29553        ) {
29554            if e.expressions.len() > 1 {
29555                self.write("(");
29556            }
29557            for (i, expr) in e.expressions.iter().enumerate() {
29558                if i > 0 {
29559                    self.write_keyword(" OR ");
29560                }
29561                self.generate_expression(expr)?;
29562                self.write_space();
29563                self.write("@@");
29564                self.write_space();
29565                self.generate_expression(&e.this)?;
29566            }
29567            if e.expressions.len() > 1 {
29568                self.write(")");
29569            }
29570            return Ok(());
29571        }
29572
29573        // MATCH(columns) AGAINST(expr [modifier])
29574        self.write_keyword("MATCH");
29575        self.write("(");
29576        for (i, expr) in e.expressions.iter().enumerate() {
29577            if i > 0 {
29578                self.write(", ");
29579            }
29580            self.generate_expression(expr)?;
29581        }
29582        self.write(")");
29583        self.write_keyword(" AGAINST");
29584        self.write("(");
29585        self.generate_expression(&e.this)?;
29586        if let Some(modifier) = &e.modifier {
29587            self.write_space();
29588            self.generate_expression(modifier)?;
29589        }
29590        self.write(")");
29591        Ok(())
29592    }
29593
29594    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29595        // Python: [window_frame] this
29596        if let Some(window_frame) = &e.window_frame {
29597            self.write(&format!("{:?}", window_frame).to_uppercase());
29598            self.write_space();
29599        }
29600        self.generate_expression(&e.this)?;
29601        Ok(())
29602    }
29603
29604    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29605        // MATERIALIZED [this]
29606        self.write_keyword("MATERIALIZED");
29607        if let Some(this) = &e.this {
29608            self.write_space();
29609            self.generate_expression(this)?;
29610        }
29611        Ok(())
29612    }
29613
29614    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29615        // MERGE INTO target USING source ON condition WHEN ...
29616        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29617        if let Some(with_) = &e.with_ {
29618            self.generate_expression(with_)?;
29619            self.write_space();
29620        }
29621        self.write_keyword("MERGE INTO");
29622        self.write_space();
29623        self.generate_expression(&e.this)?;
29624
29625        // USING clause - newline before in pretty mode
29626        if self.config.pretty {
29627            self.write_newline();
29628            self.write_indent();
29629        } else {
29630            self.write_space();
29631        }
29632        self.write_keyword("USING");
29633        self.write_space();
29634        self.generate_expression(&e.using)?;
29635
29636        // ON clause - newline before in pretty mode
29637        if let Some(on) = &e.on {
29638            if self.config.pretty {
29639                self.write_newline();
29640                self.write_indent();
29641            } else {
29642                self.write_space();
29643            }
29644            self.write_keyword("ON");
29645            self.write_space();
29646            self.generate_expression(on)?;
29647        }
29648        // DuckDB USING (key_columns) clause
29649        if let Some(using_cond) = &e.using_cond {
29650            self.write_space();
29651            self.write_keyword("USING");
29652            self.write_space();
29653            self.write("(");
29654            // using_cond is a Tuple containing the column identifiers
29655            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29656                for (i, col) in tuple.expressions.iter().enumerate() {
29657                    if i > 0 {
29658                        self.write(", ");
29659                    }
29660                    self.generate_expression(col)?;
29661                }
29662            } else {
29663                self.generate_expression(using_cond)?;
29664            }
29665            self.write(")");
29666        }
29667        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29668        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29669        if matches!(
29670            self.config.dialect,
29671            Some(crate::DialectType::PostgreSQL)
29672                | Some(crate::DialectType::Redshift)
29673                | Some(crate::DialectType::Trino)
29674                | Some(crate::DialectType::Presto)
29675                | Some(crate::DialectType::Athena)
29676        ) {
29677            let mut names = Vec::new();
29678            match e.this.as_ref() {
29679                Expression::Alias(a) => {
29680                    // e.g., "x AS z" -> strip both "x" and "z"
29681                    if let Expression::Table(t) = &a.this {
29682                        names.push(t.name.name.clone());
29683                    } else if let Expression::Identifier(id) = &a.this {
29684                        names.push(id.name.clone());
29685                    }
29686                    names.push(a.alias.name.clone());
29687                }
29688                Expression::Table(t) => {
29689                    names.push(t.name.name.clone());
29690                }
29691                Expression::Identifier(id) => {
29692                    names.push(id.name.clone());
29693                }
29694                _ => {}
29695            }
29696            self.merge_strip_qualifiers = names;
29697        }
29698
29699        // WHEN clauses - newline before each in pretty mode
29700        if let Some(whens) = &e.whens {
29701            if self.config.pretty {
29702                self.write_newline();
29703                self.write_indent();
29704            } else {
29705                self.write_space();
29706            }
29707            self.generate_expression(whens)?;
29708        }
29709
29710        // Restore merge_strip_qualifiers
29711        self.merge_strip_qualifiers = saved_merge_strip;
29712
29713        // OUTPUT/RETURNING clause - newline before in pretty mode
29714        if let Some(returning) = &e.returning {
29715            if self.config.pretty {
29716                self.write_newline();
29717                self.write_indent();
29718            } else {
29719                self.write_space();
29720            }
29721            self.generate_expression(returning)?;
29722        }
29723        Ok(())
29724    }
29725
29726    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29727        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29728        if e.no.is_some() {
29729            self.write_keyword("NO MERGEBLOCKRATIO");
29730        } else if e.default.is_some() {
29731            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29732        } else {
29733            self.write_keyword("MERGEBLOCKRATIO");
29734            self.write("=");
29735            if let Some(this) = &e.this {
29736                self.generate_expression(this)?;
29737            }
29738            if e.percent.is_some() {
29739                self.write_keyword(" PERCENT");
29740            }
29741        }
29742        Ok(())
29743    }
29744
29745    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29746        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29747        self.write_keyword("TTL");
29748        let pretty_clickhouse = self.config.pretty
29749            && matches!(
29750                self.config.dialect,
29751                Some(crate::dialects::DialectType::ClickHouse)
29752            );
29753
29754        if pretty_clickhouse {
29755            self.write_newline();
29756            self.indent_level += 1;
29757            for (i, expr) in e.expressions.iter().enumerate() {
29758                if i > 0 {
29759                    self.write(",");
29760                    self.write_newline();
29761                }
29762                self.write_indent();
29763                self.generate_expression(expr)?;
29764            }
29765            self.indent_level -= 1;
29766        } else {
29767            self.write_space();
29768            for (i, expr) in e.expressions.iter().enumerate() {
29769                if i > 0 {
29770                    self.write(", ");
29771                }
29772                self.generate_expression(expr)?;
29773            }
29774        }
29775
29776        if let Some(where_) = &e.where_ {
29777            if pretty_clickhouse {
29778                self.write_newline();
29779                if let Expression::Where(w) = where_.as_ref() {
29780                    self.write_indent();
29781                    self.write_keyword("WHERE");
29782                    self.write_newline();
29783                    self.indent_level += 1;
29784                    self.write_indent();
29785                    self.generate_expression(&w.this)?;
29786                    self.indent_level -= 1;
29787                } else {
29788                    self.write_indent();
29789                    self.generate_expression(where_)?;
29790                }
29791            } else {
29792                self.write_space();
29793                self.generate_expression(where_)?;
29794            }
29795        }
29796        if let Some(group) = &e.group {
29797            if pretty_clickhouse {
29798                self.write_newline();
29799                if let Expression::Group(g) = group.as_ref() {
29800                    self.write_indent();
29801                    self.write_keyword("GROUP BY");
29802                    self.write_newline();
29803                    self.indent_level += 1;
29804                    for (i, expr) in g.expressions.iter().enumerate() {
29805                        if i > 0 {
29806                            self.write(",");
29807                            self.write_newline();
29808                        }
29809                        self.write_indent();
29810                        self.generate_expression(expr)?;
29811                    }
29812                    self.indent_level -= 1;
29813                } else {
29814                    self.write_indent();
29815                    self.generate_expression(group)?;
29816                }
29817            } else {
29818                self.write_space();
29819                self.generate_expression(group)?;
29820            }
29821        }
29822        if let Some(aggregates) = &e.aggregates {
29823            if pretty_clickhouse {
29824                self.write_newline();
29825                self.write_indent();
29826                self.write_keyword("SET");
29827                self.write_newline();
29828                self.indent_level += 1;
29829                if let Expression::Tuple(t) = aggregates.as_ref() {
29830                    for (i, agg) in t.expressions.iter().enumerate() {
29831                        if i > 0 {
29832                            self.write(",");
29833                            self.write_newline();
29834                        }
29835                        self.write_indent();
29836                        self.generate_expression(agg)?;
29837                    }
29838                } else {
29839                    self.write_indent();
29840                    self.generate_expression(aggregates)?;
29841                }
29842                self.indent_level -= 1;
29843            } else {
29844                self.write_space();
29845                self.write_keyword("SET");
29846                self.write_space();
29847                self.generate_expression(aggregates)?;
29848            }
29849        }
29850        Ok(())
29851    }
29852
29853    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29854        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29855        self.generate_expression(&e.this)?;
29856        if e.delete.is_some() {
29857            self.write_keyword(" DELETE");
29858        }
29859        if let Some(recompress) = &e.recompress {
29860            self.write_keyword(" RECOMPRESS ");
29861            self.generate_expression(recompress)?;
29862        }
29863        if let Some(to_disk) = &e.to_disk {
29864            self.write_keyword(" TO DISK ");
29865            self.generate_expression(to_disk)?;
29866        }
29867        if let Some(to_volume) = &e.to_volume {
29868            self.write_keyword(" TO VOLUME ");
29869            self.generate_expression(to_volume)?;
29870        }
29871        Ok(())
29872    }
29873
29874    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29875        // MINHASH(this, expressions...)
29876        self.write_keyword("MINHASH");
29877        self.write("(");
29878        self.generate_expression(&e.this)?;
29879        for expr in &e.expressions {
29880            self.write(", ");
29881            self.generate_expression(expr)?;
29882        }
29883        self.write(")");
29884        Ok(())
29885    }
29886
29887    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29888        // model!attribute - Snowflake syntax
29889        self.generate_expression(&e.this)?;
29890        self.write("!");
29891        self.generate_expression(&e.expression)?;
29892        Ok(())
29893    }
29894
29895    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29896        // MONTHNAME(this)
29897        self.write_keyword("MONTHNAME");
29898        self.write("(");
29899        self.generate_expression(&e.this)?;
29900        self.write(")");
29901        Ok(())
29902    }
29903
29904    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29905        // Output leading comments
29906        for comment in &e.leading_comments {
29907            self.write_formatted_comment(comment);
29908            if self.config.pretty {
29909                self.write_newline();
29910                self.write_indent();
29911            } else {
29912                self.write_space();
29913            }
29914        }
29915        // Python: INSERT kind expressions source
29916        self.write_keyword("INSERT");
29917        self.write_space();
29918        self.write(&e.kind);
29919        if self.config.pretty {
29920            self.indent_level += 1;
29921            for expr in &e.expressions {
29922                self.write_newline();
29923                self.write_indent();
29924                self.generate_expression(expr)?;
29925            }
29926            self.indent_level -= 1;
29927        } else {
29928            for expr in &e.expressions {
29929                self.write_space();
29930                self.generate_expression(expr)?;
29931            }
29932        }
29933        if let Some(source) = &e.source {
29934            if self.config.pretty {
29935                self.write_newline();
29936                self.write_indent();
29937            } else {
29938                self.write_space();
29939            }
29940            self.generate_expression(source)?;
29941        }
29942        Ok(())
29943    }
29944
29945    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29946        // Python: NEXT VALUE FOR this [OVER (order)]
29947        self.write_keyword("NEXT VALUE FOR");
29948        self.write_space();
29949        self.generate_expression(&e.this)?;
29950        if let Some(order) = &e.order {
29951            self.write_space();
29952            self.write_keyword("OVER");
29953            self.write(" (");
29954            self.generate_expression(order)?;
29955            self.write(")");
29956        }
29957        Ok(())
29958    }
29959
29960    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29961        // NORMAL(mean, stddev, gen)
29962        self.write_keyword("NORMAL");
29963        self.write("(");
29964        self.generate_expression(&e.this)?;
29965        if let Some(stddev) = &e.stddev {
29966            self.write(", ");
29967            self.generate_expression(stddev)?;
29968        }
29969        if let Some(gen) = &e.gen {
29970            self.write(", ");
29971            self.generate_expression(gen)?;
29972        }
29973        self.write(")");
29974        Ok(())
29975    }
29976
29977    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29978        // NORMALIZE(this, form) or CASEFOLD version
29979        if e.is_casefold.is_some() {
29980            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29981        } else {
29982            self.write_keyword("NORMALIZE");
29983        }
29984        self.write("(");
29985        self.generate_expression(&e.this)?;
29986        if let Some(form) = &e.form {
29987            self.write(", ");
29988            self.generate_expression(form)?;
29989        }
29990        self.write(")");
29991        Ok(())
29992    }
29993
29994    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29995        // Python: [NOT ]NULL
29996        if e.allow_null.is_none() {
29997            self.write_keyword("NOT ");
29998        }
29999        self.write_keyword("NULL");
30000        Ok(())
30001    }
30002
30003    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
30004        // NULLIF(this, expression)
30005        self.write_keyword("NULLIF");
30006        self.write("(");
30007        self.generate_expression(&e.this)?;
30008        self.write(", ");
30009        self.generate_expression(&e.expression)?;
30010        self.write(")");
30011        Ok(())
30012    }
30013
30014    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
30015        // FORMAT(this, format, culture)
30016        self.write_keyword("FORMAT");
30017        self.write("(");
30018        self.generate_expression(&e.this)?;
30019        self.write(", '");
30020        self.write(&e.format);
30021        self.write("'");
30022        if let Some(culture) = &e.culture {
30023            self.write(", ");
30024            self.generate_expression(culture)?;
30025        }
30026        self.write(")");
30027        Ok(())
30028    }
30029
30030    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
30031        // OBJECT_AGG(key, value)
30032        self.write_keyword("OBJECT_AGG");
30033        self.write("(");
30034        self.generate_expression(&e.this)?;
30035        self.write(", ");
30036        self.generate_expression(&e.expression)?;
30037        self.write(")");
30038        Ok(())
30039    }
30040
30041    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
30042        // Python: Just returns the name
30043        self.generate_expression(&e.this)?;
30044        Ok(())
30045    }
30046
30047    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
30048        // OBJECT_INSERT(obj, key, value, [update_flag])
30049        self.write_keyword("OBJECT_INSERT");
30050        self.write("(");
30051        self.generate_expression(&e.this)?;
30052        if let Some(key) = &e.key {
30053            self.write(", ");
30054            self.generate_expression(key)?;
30055        }
30056        if let Some(value) = &e.value {
30057            self.write(", ");
30058            self.generate_expression(value)?;
30059        }
30060        if let Some(update_flag) = &e.update_flag {
30061            self.write(", ");
30062            self.generate_expression(update_flag)?;
30063        }
30064        self.write(")");
30065        Ok(())
30066    }
30067
30068    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
30069        // OFFSET value [ROW|ROWS]
30070        self.write_keyword("OFFSET");
30071        self.write_space();
30072        self.generate_expression(&e.this)?;
30073        // Output ROWS keyword only for TSQL/Oracle targets
30074        if e.rows == Some(true)
30075            && matches!(
30076                self.config.dialect,
30077                Some(crate::dialects::DialectType::TSQL)
30078                    | Some(crate::dialects::DialectType::Oracle)
30079            )
30080        {
30081            self.write_space();
30082            self.write_keyword("ROWS");
30083        }
30084        Ok(())
30085    }
30086
30087    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30088        // QUALIFY condition (Snowflake/BigQuery)
30089        self.write_keyword("QUALIFY");
30090        self.write_space();
30091        self.generate_expression(&e.this)?;
30092        Ok(())
30093    }
30094
30095    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30096        // ON CLUSTER cluster_name
30097        self.write_keyword("ON CLUSTER");
30098        self.write_space();
30099        self.generate_expression(&e.this)?;
30100        Ok(())
30101    }
30102
30103    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30104        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30105        self.write_keyword("ON COMMIT");
30106        if e.delete.is_some() {
30107            self.write_keyword(" DELETE ROWS");
30108        } else {
30109            self.write_keyword(" PRESERVE ROWS");
30110        }
30111        Ok(())
30112    }
30113
30114    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30115        // Python: error/empty/null handling
30116        if let Some(empty) = &e.empty {
30117            self.generate_expression(empty)?;
30118            self.write_keyword(" ON EMPTY");
30119        }
30120        if let Some(error) = &e.error {
30121            if e.empty.is_some() {
30122                self.write_space();
30123            }
30124            self.generate_expression(error)?;
30125            self.write_keyword(" ON ERROR");
30126        }
30127        if let Some(null) = &e.null {
30128            if e.empty.is_some() || e.error.is_some() {
30129                self.write_space();
30130            }
30131            self.generate_expression(null)?;
30132            self.write_keyword(" ON NULL");
30133        }
30134        Ok(())
30135    }
30136
30137    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30138        // Materialize doesn't support ON CONFLICT - skip entirely
30139        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30140            return Ok(());
30141        }
30142        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30143        if e.duplicate.is_some() {
30144            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30145            self.write_keyword("ON DUPLICATE KEY UPDATE");
30146            for (i, expr) in e.expressions.iter().enumerate() {
30147                if i > 0 {
30148                    self.write(",");
30149                }
30150                self.write_space();
30151                self.generate_expression(expr)?;
30152            }
30153            return Ok(());
30154        } else {
30155            self.write_keyword("ON CONFLICT");
30156        }
30157        if let Some(constraint) = &e.constraint {
30158            self.write_keyword(" ON CONSTRAINT ");
30159            self.generate_expression(constraint)?;
30160        }
30161        if let Some(conflict_keys) = &e.conflict_keys {
30162            // conflict_keys can be a Tuple containing expressions
30163            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30164                self.write("(");
30165                for (i, expr) in t.expressions.iter().enumerate() {
30166                    if i > 0 {
30167                        self.write(", ");
30168                    }
30169                    self.generate_expression(expr)?;
30170                }
30171                self.write(")");
30172            } else {
30173                self.write("(");
30174                self.generate_expression(conflict_keys)?;
30175                self.write(")");
30176            }
30177        }
30178        if let Some(index_predicate) = &e.index_predicate {
30179            self.write_keyword(" WHERE ");
30180            self.generate_expression(index_predicate)?;
30181        }
30182        if let Some(action) = &e.action {
30183            // Check if action is "NOTHING" or an UPDATE set
30184            if let Expression::Identifier(id) = action.as_ref() {
30185                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30186                    self.write_keyword(" DO NOTHING");
30187                } else {
30188                    self.write_keyword(" DO ");
30189                    self.generate_expression(action)?;
30190                }
30191            } else if let Expression::Tuple(t) = action.as_ref() {
30192                // DO UPDATE SET col1 = val1, col2 = val2
30193                self.write_keyword(" DO UPDATE SET ");
30194                for (i, expr) in t.expressions.iter().enumerate() {
30195                    if i > 0 {
30196                        self.write(", ");
30197                    }
30198                    self.generate_expression(expr)?;
30199                }
30200            } else {
30201                self.write_keyword(" DO ");
30202                self.generate_expression(action)?;
30203            }
30204        }
30205        // WHERE clause for the UPDATE action
30206        if let Some(where_) = &e.where_ {
30207            self.write_keyword(" WHERE ");
30208            self.generate_expression(where_)?;
30209        }
30210        Ok(())
30211    }
30212
30213    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30214        // ON property_value
30215        self.write_keyword("ON");
30216        self.write_space();
30217        self.generate_expression(&e.this)?;
30218        Ok(())
30219    }
30220
30221    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30222        // Python: this expression (e.g., column opclass)
30223        self.generate_expression(&e.this)?;
30224        self.write_space();
30225        self.generate_expression(&e.expression)?;
30226        Ok(())
30227    }
30228
30229    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30230        // Python: OPENJSON(this[, path]) [WITH (columns)]
30231        self.write_keyword("OPENJSON");
30232        self.write("(");
30233        self.generate_expression(&e.this)?;
30234        if let Some(path) = &e.path {
30235            self.write(", ");
30236            self.generate_expression(path)?;
30237        }
30238        self.write(")");
30239        if !e.expressions.is_empty() {
30240            self.write_keyword(" WITH");
30241            if self.config.pretty {
30242                self.write(" (\n");
30243                self.indent_level += 2;
30244                for (i, expr) in e.expressions.iter().enumerate() {
30245                    if i > 0 {
30246                        self.write(",\n");
30247                    }
30248                    self.write_indent();
30249                    self.generate_expression(expr)?;
30250                }
30251                self.write("\n");
30252                self.indent_level -= 2;
30253                self.write(")");
30254            } else {
30255                self.write(" (");
30256                for (i, expr) in e.expressions.iter().enumerate() {
30257                    if i > 0 {
30258                        self.write(", ");
30259                    }
30260                    self.generate_expression(expr)?;
30261                }
30262                self.write(")");
30263            }
30264        }
30265        Ok(())
30266    }
30267
30268    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30269        // Python: this kind [path] [AS JSON]
30270        self.generate_expression(&e.this)?;
30271        self.write_space();
30272        // Use parsed data_type if available, otherwise fall back to kind string
30273        if let Some(ref dt) = e.data_type {
30274            self.generate_data_type(dt)?;
30275        } else if !e.kind.is_empty() {
30276            self.write(&e.kind);
30277        }
30278        if let Some(path) = &e.path {
30279            self.write_space();
30280            self.generate_expression(path)?;
30281        }
30282        if e.as_json.is_some() {
30283            self.write_keyword(" AS JSON");
30284        }
30285        Ok(())
30286    }
30287
30288    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30289        // this OPERATOR(op) expression
30290        self.generate_expression(&e.this)?;
30291        self.write_space();
30292        if let Some(op) = &e.operator {
30293            self.write_keyword("OPERATOR");
30294            self.write("(");
30295            self.generate_expression(op)?;
30296            self.write(")");
30297        }
30298        // Emit inline comments between OPERATOR() and the RHS
30299        for comment in &e.comments {
30300            self.write_space();
30301            self.write_formatted_comment(comment);
30302        }
30303        self.write_space();
30304        self.generate_expression(&e.expression)?;
30305        Ok(())
30306    }
30307
30308    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30309        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30310        self.write_keyword("ORDER BY");
30311        let pretty_clickhouse_single_paren = self.config.pretty
30312            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30313            && e.expressions.len() == 1
30314            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30315        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30316            && e.expressions.len() == 1
30317            && matches!(e.expressions[0].this, Expression::Tuple(_))
30318            && !e.expressions[0].desc
30319            && e.expressions[0].nulls_first.is_none();
30320
30321        if pretty_clickhouse_single_paren {
30322            self.write_space();
30323            if let Expression::Paren(p) = &e.expressions[0].this {
30324                self.write("(");
30325                self.write_newline();
30326                self.indent_level += 1;
30327                self.write_indent();
30328                self.generate_expression(&p.this)?;
30329                self.indent_level -= 1;
30330                self.write_newline();
30331                self.write(")");
30332            }
30333            return Ok(());
30334        }
30335
30336        if clickhouse_single_tuple {
30337            self.write_space();
30338            if let Expression::Tuple(t) = &e.expressions[0].this {
30339                self.write("(");
30340                for (i, expr) in t.expressions.iter().enumerate() {
30341                    if i > 0 {
30342                        self.write(", ");
30343                    }
30344                    self.generate_expression(expr)?;
30345                }
30346                self.write(")");
30347            }
30348            return Ok(());
30349        }
30350
30351        self.write_space();
30352        for (i, ordered) in e.expressions.iter().enumerate() {
30353            if i > 0 {
30354                self.write(", ");
30355            }
30356            self.generate_expression(&ordered.this)?;
30357            if ordered.desc {
30358                self.write_space();
30359                self.write_keyword("DESC");
30360            } else if ordered.explicit_asc {
30361                self.write_space();
30362                self.write_keyword("ASC");
30363            }
30364            if let Some(nulls_first) = ordered.nulls_first {
30365                // In Dremio, NULLS LAST is the default, so skip generating it
30366                let skip_nulls_last =
30367                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30368                if !skip_nulls_last {
30369                    self.write_space();
30370                    self.write_keyword("NULLS");
30371                    self.write_space();
30372                    if nulls_first {
30373                        self.write_keyword("FIRST");
30374                    } else {
30375                        self.write_keyword("LAST");
30376                    }
30377                }
30378            }
30379        }
30380        Ok(())
30381    }
30382
30383    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30384        // OUTPUT(model)
30385        self.write_keyword("OUTPUT");
30386        self.write("(");
30387        if self.config.pretty {
30388            self.indent_level += 1;
30389            self.write_newline();
30390            self.write_indent();
30391            self.generate_expression(&e.this)?;
30392            self.indent_level -= 1;
30393            self.write_newline();
30394        } else {
30395            self.generate_expression(&e.this)?;
30396        }
30397        self.write(")");
30398        Ok(())
30399    }
30400
30401    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30402        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30403        self.write_keyword("TRUNCATE");
30404        if let Some(this) = &e.this {
30405            self.write_space();
30406            self.generate_expression(this)?;
30407        }
30408        if e.with_count.is_some() {
30409            self.write_keyword(" WITH COUNT");
30410        } else {
30411            self.write_keyword(" WITHOUT COUNT");
30412        }
30413        Ok(())
30414    }
30415
30416    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30417        // Python: name(expressions)(params)
30418        self.generate_expression(&e.this)?;
30419        self.write("(");
30420        for (i, expr) in e.expressions.iter().enumerate() {
30421            if i > 0 {
30422                self.write(", ");
30423            }
30424            self.generate_expression(expr)?;
30425        }
30426        self.write(")(");
30427        for (i, param) in e.params.iter().enumerate() {
30428            if i > 0 {
30429                self.write(", ");
30430            }
30431            self.generate_expression(param)?;
30432        }
30433        self.write(")");
30434        Ok(())
30435    }
30436
30437    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30438        // PARSE_DATETIME(format, this) or similar
30439        self.write_keyword("PARSE_DATETIME");
30440        self.write("(");
30441        if let Some(format) = &e.format {
30442            self.write("'");
30443            self.write(format);
30444            self.write("', ");
30445        }
30446        self.generate_expression(&e.this)?;
30447        if let Some(zone) = &e.zone {
30448            self.write(", ");
30449            self.generate_expression(zone)?;
30450        }
30451        self.write(")");
30452        Ok(())
30453    }
30454
30455    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30456        // PARSE_IP(this, type, permissive)
30457        self.write_keyword("PARSE_IP");
30458        self.write("(");
30459        self.generate_expression(&e.this)?;
30460        if let Some(type_) = &e.type_ {
30461            self.write(", ");
30462            self.generate_expression(type_)?;
30463        }
30464        if let Some(permissive) = &e.permissive {
30465            self.write(", ");
30466            self.generate_expression(permissive)?;
30467        }
30468        self.write(")");
30469        Ok(())
30470    }
30471
30472    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30473        // PARSE_JSON(this, [expression])
30474        self.write_keyword("PARSE_JSON");
30475        self.write("(");
30476        self.generate_expression(&e.this)?;
30477        if let Some(expression) = &e.expression {
30478            self.write(", ");
30479            self.generate_expression(expression)?;
30480        }
30481        self.write(")");
30482        Ok(())
30483    }
30484
30485    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30486        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30487        self.write_keyword("PARSE_TIME");
30488        self.write("(");
30489        self.write(&format!("'{}'", e.format));
30490        self.write(", ");
30491        self.generate_expression(&e.this)?;
30492        self.write(")");
30493        Ok(())
30494    }
30495
30496    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30497        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30498        self.write_keyword("PARSE_URL");
30499        self.write("(");
30500        self.generate_expression(&e.this)?;
30501        if let Some(part) = &e.part_to_extract {
30502            self.write(", ");
30503            self.generate_expression(part)?;
30504        }
30505        if let Some(key) = &e.key {
30506            self.write(", ");
30507            self.generate_expression(key)?;
30508        }
30509        if let Some(permissive) = &e.permissive {
30510            self.write(", ");
30511            self.generate_expression(permissive)?;
30512        }
30513        self.write(")");
30514        Ok(())
30515    }
30516
30517    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30518        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30519        if e.subpartition {
30520            self.write_keyword("SUBPARTITION");
30521        } else {
30522            self.write_keyword("PARTITION");
30523        }
30524        self.write("(");
30525        for (i, expr) in e.expressions.iter().enumerate() {
30526            if i > 0 {
30527                self.write(", ");
30528            }
30529            self.generate_expression(expr)?;
30530        }
30531        self.write(")");
30532        Ok(())
30533    }
30534
30535    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30536        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30537        if let Some(this) = &e.this {
30538            if let Some(expression) = &e.expression {
30539                // WITH (MODULUS this, REMAINDER expression)
30540                self.write_keyword("WITH");
30541                self.write(" (");
30542                self.write_keyword("MODULUS");
30543                self.write_space();
30544                self.generate_expression(this)?;
30545                self.write(", ");
30546                self.write_keyword("REMAINDER");
30547                self.write_space();
30548                self.generate_expression(expression)?;
30549                self.write(")");
30550            } else {
30551                // IN (this) - this could be a list
30552                self.write_keyword("IN");
30553                self.write(" (");
30554                self.generate_partition_bound_values(this)?;
30555                self.write(")");
30556            }
30557        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30558            // FROM (from_expressions) TO (to_expressions)
30559            self.write_keyword("FROM");
30560            self.write(" (");
30561            self.generate_partition_bound_values(from)?;
30562            self.write(") ");
30563            self.write_keyword("TO");
30564            self.write(" (");
30565            self.generate_partition_bound_values(to)?;
30566            self.write(")");
30567        }
30568        Ok(())
30569    }
30570
30571    /// Generate partition bound values - handles Tuple expressions by outputting
30572    /// contents without wrapping parens (since caller provides the parens)
30573    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30574        if let Expression::Tuple(t) = expr {
30575            for (i, e) in t.expressions.iter().enumerate() {
30576                if i > 0 {
30577                    self.write(", ");
30578                }
30579                self.generate_expression(e)?;
30580            }
30581            Ok(())
30582        } else {
30583            self.generate_expression(expr)
30584        }
30585    }
30586
30587    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30588        // PARTITION BY LIST (partition_expressions) (create_expressions)
30589        self.write_keyword("PARTITION BY LIST");
30590        if let Some(partition_exprs) = &e.partition_expressions {
30591            self.write(" (");
30592            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30593            self.generate_doris_partition_expressions(partition_exprs)?;
30594            self.write(")");
30595        }
30596        if let Some(create_exprs) = &e.create_expressions {
30597            self.write(" (");
30598            // Unwrap Tuple for partition definitions
30599            self.generate_doris_partition_definitions(create_exprs)?;
30600            self.write(")");
30601        }
30602        Ok(())
30603    }
30604
30605    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30606        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30607        self.write_keyword("PARTITION BY RANGE");
30608        if let Some(partition_exprs) = &e.partition_expressions {
30609            self.write(" (");
30610            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30611            self.generate_doris_partition_expressions(partition_exprs)?;
30612            self.write(")");
30613        }
30614        if let Some(create_exprs) = &e.create_expressions {
30615            self.write(" (");
30616            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30617            self.generate_doris_partition_definitions(create_exprs)?;
30618            self.write(")");
30619        }
30620        Ok(())
30621    }
30622
30623    /// Generate Doris partition column expressions (unwrap Tuple)
30624    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30625        if let Expression::Tuple(t) = expr {
30626            for (i, e) in t.expressions.iter().enumerate() {
30627                if i > 0 {
30628                    self.write(", ");
30629                }
30630                self.generate_expression(e)?;
30631            }
30632        } else {
30633            self.generate_expression(expr)?;
30634        }
30635        Ok(())
30636    }
30637
30638    /// Generate Doris partition definitions (comma-separated Partition expressions)
30639    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30640        match expr {
30641            Expression::Tuple(t) => {
30642                // Multiple partitions, comma-separated
30643                for (i, part) in t.expressions.iter().enumerate() {
30644                    if i > 0 {
30645                        self.write(", ");
30646                    }
30647                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30648                    if let Expression::Partition(p) = part {
30649                        for (j, inner) in p.expressions.iter().enumerate() {
30650                            if j > 0 {
30651                                self.write(", ");
30652                            }
30653                            self.generate_expression(inner)?;
30654                        }
30655                    } else {
30656                        self.generate_expression(part)?;
30657                    }
30658                }
30659            }
30660            Expression::PartitionByRangePropertyDynamic(_) => {
30661                // Dynamic partition - FROM/TO/INTERVAL
30662                self.generate_expression(expr)?;
30663            }
30664            _ => {
30665                self.generate_expression(expr)?;
30666            }
30667        }
30668        Ok(())
30669    }
30670
30671    fn generate_partition_by_range_property_dynamic(
30672        &mut self,
30673        e: &PartitionByRangePropertyDynamic,
30674    ) -> Result<()> {
30675        if e.use_start_end {
30676            // StarRocks: START ('val') END ('val') EVERY (expr)
30677            if let Some(start) = &e.start {
30678                self.write_keyword("START");
30679                self.write(" (");
30680                self.generate_expression(start)?;
30681                self.write(")");
30682            }
30683            if let Some(end) = &e.end {
30684                self.write_space();
30685                self.write_keyword("END");
30686                self.write(" (");
30687                self.generate_expression(end)?;
30688                self.write(")");
30689            }
30690            if let Some(every) = &e.every {
30691                self.write_space();
30692                self.write_keyword("EVERY");
30693                self.write(" (");
30694                // Use unquoted interval format for StarRocks
30695                self.generate_doris_interval(every)?;
30696                self.write(")");
30697            }
30698        } else {
30699            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30700            if let Some(start) = &e.start {
30701                self.write_keyword("FROM");
30702                self.write(" (");
30703                self.generate_expression(start)?;
30704                self.write(")");
30705            }
30706            if let Some(end) = &e.end {
30707                self.write_space();
30708                self.write_keyword("TO");
30709                self.write(" (");
30710                self.generate_expression(end)?;
30711                self.write(")");
30712            }
30713            if let Some(every) = &e.every {
30714                self.write_space();
30715                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30716                self.generate_doris_interval(every)?;
30717            }
30718        }
30719        Ok(())
30720    }
30721
30722    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30723    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30724        if let Expression::Interval(interval) = expr {
30725            self.write_keyword("INTERVAL");
30726            if let Some(ref value) = interval.this {
30727                self.write_space();
30728                // If the value is a string literal that looks like a number,
30729                // output it without quotes (matching Python sqlglot's
30730                // partitionbyrangepropertydynamic_sql which converts back to number)
30731                match value {
30732                    Expression::Literal(Literal::String(s))
30733                        if s.chars()
30734                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30735                            && !s.is_empty() =>
30736                    {
30737                        self.write(s);
30738                    }
30739                    _ => {
30740                        self.generate_expression(value)?;
30741                    }
30742                }
30743            }
30744            if let Some(ref unit_spec) = interval.unit {
30745                self.write_space();
30746                self.write_interval_unit_spec(unit_spec)?;
30747            }
30748            Ok(())
30749        } else {
30750            self.generate_expression(expr)
30751        }
30752    }
30753
30754    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30755        // TRUNCATE(expression, this)
30756        self.write_keyword("TRUNCATE");
30757        self.write("(");
30758        self.generate_expression(&e.expression)?;
30759        self.write(", ");
30760        self.generate_expression(&e.this)?;
30761        self.write(")");
30762        Ok(())
30763    }
30764
30765    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30766        // Doris: PARTITION name VALUES IN (val1, val2)
30767        self.write_keyword("PARTITION");
30768        self.write_space();
30769        self.generate_expression(&e.this)?;
30770        self.write_space();
30771        self.write_keyword("VALUES IN");
30772        self.write(" (");
30773        for (i, expr) in e.expressions.iter().enumerate() {
30774            if i > 0 {
30775                self.write(", ");
30776            }
30777            self.generate_expression(expr)?;
30778        }
30779        self.write(")");
30780        Ok(())
30781    }
30782
30783    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30784        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30785        // TSQL ranges have no expressions and just use `this TO expression`
30786        if e.expressions.is_empty() && e.expression.is_some() {
30787            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30788            self.generate_expression(&e.this)?;
30789            self.write_space();
30790            self.write_keyword("TO");
30791            self.write_space();
30792            self.generate_expression(e.expression.as_ref().unwrap())?;
30793            return Ok(());
30794        }
30795
30796        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30797        self.write_keyword("PARTITION");
30798        self.write_space();
30799        self.generate_expression(&e.this)?;
30800        self.write_space();
30801
30802        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30803        if e.expressions.len() == 1 {
30804            // Single value: VALUES LESS THAN (val)
30805            self.write_keyword("VALUES LESS THAN");
30806            self.write(" (");
30807            self.generate_expression(&e.expressions[0])?;
30808            self.write(")");
30809        } else if !e.expressions.is_empty() {
30810            // Multiple values with Tuple: VALUES [(val1), (val2))
30811            self.write_keyword("VALUES");
30812            self.write(" [");
30813            for (i, expr) in e.expressions.iter().enumerate() {
30814                if i > 0 {
30815                    self.write(", ");
30816                }
30817                // If the expr is a Tuple, generate its contents wrapped in parens
30818                if let Expression::Tuple(t) = expr {
30819                    self.write("(");
30820                    for (j, inner) in t.expressions.iter().enumerate() {
30821                        if j > 0 {
30822                            self.write(", ");
30823                        }
30824                        self.generate_expression(inner)?;
30825                    }
30826                    self.write(")");
30827                } else {
30828                    self.write("(");
30829                    self.generate_expression(expr)?;
30830                    self.write(")");
30831                }
30832            }
30833            self.write(")");
30834        }
30835        Ok(())
30836    }
30837
30838    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30839        // BUCKET(this, expression)
30840        self.write_keyword("BUCKET");
30841        self.write("(");
30842        self.generate_expression(&e.this)?;
30843        self.write(", ");
30844        self.generate_expression(&e.expression)?;
30845        self.write(")");
30846        Ok(())
30847    }
30848
30849    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
30850        // BigQuery table property: PARTITION BY expression [, expression ...]
30851        self.write_keyword("PARTITION BY");
30852        self.write_space();
30853        for (i, expr) in e.expressions.iter().enumerate() {
30854            if i > 0 {
30855                self.write(", ");
30856            }
30857            self.generate_expression(expr)?;
30858        }
30859        Ok(())
30860    }
30861
30862    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30863        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30864        if matches!(
30865            self.config.dialect,
30866            Some(crate::dialects::DialectType::Teradata)
30867                | Some(crate::dialects::DialectType::ClickHouse)
30868        ) {
30869            self.write_keyword("PARTITION BY");
30870        } else {
30871            self.write_keyword("PARTITIONED BY");
30872        }
30873        self.write_space();
30874        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30875        if self.config.pretty {
30876            if let Expression::Tuple(ref tuple) = *e.this {
30877                self.write("(");
30878                self.write_newline();
30879                self.indent_level += 1;
30880                for (i, expr) in tuple.expressions.iter().enumerate() {
30881                    if i > 0 {
30882                        self.write(",");
30883                        self.write_newline();
30884                    }
30885                    self.write_indent();
30886                    self.generate_expression(expr)?;
30887                }
30888                self.indent_level -= 1;
30889                self.write_newline();
30890                self.write(")");
30891            } else {
30892                self.generate_expression(&e.this)?;
30893            }
30894        } else {
30895            self.generate_expression(&e.this)?;
30896        }
30897        Ok(())
30898    }
30899
30900    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30901        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30902        self.write_keyword("PARTITION OF");
30903        self.write_space();
30904        self.generate_expression(&e.this)?;
30905        // Check if expression is a PartitionBoundSpec
30906        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30907            self.write_space();
30908            self.write_keyword("FOR VALUES");
30909            self.write_space();
30910            self.generate_expression(&e.expression)?;
30911        } else {
30912            self.write_space();
30913            self.write_keyword("DEFAULT");
30914        }
30915        Ok(())
30916    }
30917
30918    fn generate_period_for_system_time_constraint(
30919        &mut self,
30920        e: &PeriodForSystemTimeConstraint,
30921    ) -> Result<()> {
30922        // PERIOD FOR SYSTEM_TIME (this, expression)
30923        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30924        self.write(" (");
30925        self.generate_expression(&e.this)?;
30926        self.write(", ");
30927        self.generate_expression(&e.expression)?;
30928        self.write(")");
30929        Ok(())
30930    }
30931
30932    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30933        // value AS alias
30934        // The alias can be an identifier or an expression (e.g., string concatenation)
30935        self.generate_expression(&e.this)?;
30936        self.write_space();
30937        self.write_keyword("AS");
30938        self.write_space();
30939        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30940        if self.config.unpivot_aliases_are_identifiers {
30941            match &e.alias {
30942                Expression::Literal(Literal::String(s)) => {
30943                    // Convert string literal to identifier
30944                    self.generate_identifier(&Identifier::new(s.clone()))?;
30945                }
30946                Expression::Literal(Literal::Number(n)) => {
30947                    // Convert number literal to quoted identifier
30948                    let mut id = Identifier::new(n.clone());
30949                    id.quoted = true;
30950                    self.generate_identifier(&id)?;
30951                }
30952                other => {
30953                    self.generate_expression(other)?;
30954                }
30955            }
30956        } else {
30957            self.generate_expression(&e.alias)?;
30958        }
30959        Ok(())
30960    }
30961
30962    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30963        // ANY or ANY [expression]
30964        self.write_keyword("ANY");
30965        if let Some(this) = &e.this {
30966            self.write_space();
30967            self.generate_expression(this)?;
30968        }
30969        Ok(())
30970    }
30971
30972    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30973        // ML.PREDICT(MODEL this, expression, [params_struct])
30974        self.write_keyword("ML.PREDICT");
30975        self.write("(");
30976        self.write_keyword("MODEL");
30977        self.write_space();
30978        self.generate_expression(&e.this)?;
30979        self.write(", ");
30980        self.generate_expression(&e.expression)?;
30981        if let Some(params) = &e.params_struct {
30982            self.write(", ");
30983            self.generate_expression(params)?;
30984        }
30985        self.write(")");
30986        Ok(())
30987    }
30988
30989    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30990        // PREVIOUS_DAY(this, expression)
30991        self.write_keyword("PREVIOUS_DAY");
30992        self.write("(");
30993        self.generate_expression(&e.this)?;
30994        self.write(", ");
30995        self.generate_expression(&e.expression)?;
30996        self.write(")");
30997        Ok(())
30998    }
30999
31000    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
31001        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
31002        self.write_keyword("PRIMARY KEY");
31003        if let Some(name) = &e.this {
31004            self.write_space();
31005            self.generate_expression(name)?;
31006        }
31007        if !e.expressions.is_empty() {
31008            self.write(" (");
31009            for (i, expr) in e.expressions.iter().enumerate() {
31010                if i > 0 {
31011                    self.write(", ");
31012                }
31013                self.generate_expression(expr)?;
31014            }
31015            self.write(")");
31016        }
31017        if let Some(include) = &e.include {
31018            self.write_space();
31019            self.generate_expression(include)?;
31020        }
31021        if !e.options.is_empty() {
31022            self.write_space();
31023            for (i, opt) in e.options.iter().enumerate() {
31024                if i > 0 {
31025                    self.write_space();
31026                }
31027                self.generate_expression(opt)?;
31028            }
31029        }
31030        Ok(())
31031    }
31032
31033    fn generate_primary_key_column_constraint(
31034        &mut self,
31035        _e: &PrimaryKeyColumnConstraint,
31036    ) -> Result<()> {
31037        // PRIMARY KEY constraint at column level
31038        self.write_keyword("PRIMARY KEY");
31039        Ok(())
31040    }
31041
31042    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
31043        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
31044        self.write_keyword("PATH");
31045        self.write_space();
31046        self.generate_expression(&e.this)?;
31047        Ok(())
31048    }
31049
31050    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
31051        // PROJECTION this (expression)
31052        self.write_keyword("PROJECTION");
31053        self.write_space();
31054        self.generate_expression(&e.this)?;
31055        self.write(" (");
31056        self.generate_expression(&e.expression)?;
31057        self.write(")");
31058        Ok(())
31059    }
31060
31061    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
31062        // Properties list
31063        for (i, prop) in e.expressions.iter().enumerate() {
31064            if i > 0 {
31065                self.write(", ");
31066            }
31067            self.generate_expression(prop)?;
31068        }
31069        Ok(())
31070    }
31071
31072    fn generate_property(&mut self, e: &Property) -> Result<()> {
31073        // name=value
31074        self.generate_expression(&e.this)?;
31075        if let Some(value) = &e.value {
31076            self.write("=");
31077            self.generate_expression(value)?;
31078        }
31079        Ok(())
31080    }
31081
31082    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
31083        self.write_keyword("OPTIONS");
31084        if e.entries.is_empty() {
31085            self.write(" ()");
31086            return Ok(());
31087        }
31088
31089        if self.config.pretty {
31090            self.write(" (");
31091            self.write_newline();
31092            self.indent_level += 1;
31093            for (i, entry) in e.entries.iter().enumerate() {
31094                if i > 0 {
31095                    self.write(",");
31096                    self.write_newline();
31097                }
31098                self.write_indent();
31099                self.generate_identifier(&entry.key)?;
31100                self.write("=");
31101                self.generate_expression(&entry.value)?;
31102            }
31103            self.indent_level -= 1;
31104            self.write_newline();
31105            self.write(")");
31106        } else {
31107            self.write(" (");
31108            for (i, entry) in e.entries.iter().enumerate() {
31109                if i > 0 {
31110                    self.write(", ");
31111                }
31112                self.generate_identifier(&entry.key)?;
31113                self.write("=");
31114                self.generate_expression(&entry.value)?;
31115            }
31116            self.write(")");
31117        }
31118        Ok(())
31119    }
31120
31121    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
31122    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
31123        self.write_keyword("OPTIONS");
31124        self.write(" (");
31125        for (i, opt) in options.iter().enumerate() {
31126            if i > 0 {
31127                self.write(", ");
31128            }
31129            self.generate_option_expression(opt)?;
31130        }
31131        self.write(")");
31132        Ok(())
31133    }
31134
31135    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31136    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31137        self.write_keyword("PROPERTIES");
31138        self.write(" (");
31139        for (i, prop) in properties.iter().enumerate() {
31140            if i > 0 {
31141                self.write(", ");
31142            }
31143            self.generate_option_expression(prop)?;
31144        }
31145        self.write(")");
31146        Ok(())
31147    }
31148
31149    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31150    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31151        self.write_keyword("ENVIRONMENT");
31152        self.write(" (");
31153        for (i, env_item) in environment.iter().enumerate() {
31154            if i > 0 {
31155                self.write(", ");
31156            }
31157            self.generate_environment_expression(env_item)?;
31158        }
31159        self.write(")");
31160        Ok(())
31161    }
31162
31163    /// Generate an environment expression with spaces around =
31164    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31165        match expr {
31166            Expression::Eq(eq) => {
31167                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31168                self.generate_expression(&eq.left)?;
31169                self.write(" = ");
31170                self.generate_expression(&eq.right)?;
31171                Ok(())
31172            }
31173            _ => self.generate_expression(expr),
31174        }
31175    }
31176
31177    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31178    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31179        self.write_keyword("TBLPROPERTIES");
31180        if self.config.pretty {
31181            self.write(" (");
31182            self.write_newline();
31183            self.indent_level += 1;
31184            for (i, opt) in options.iter().enumerate() {
31185                if i > 0 {
31186                    self.write(",");
31187                    self.write_newline();
31188                }
31189                self.write_indent();
31190                self.generate_option_expression(opt)?;
31191            }
31192            self.indent_level -= 1;
31193            self.write_newline();
31194            self.write(")");
31195        } else {
31196            self.write(" (");
31197            for (i, opt) in options.iter().enumerate() {
31198                if i > 0 {
31199                    self.write(", ");
31200                }
31201                self.generate_option_expression(opt)?;
31202            }
31203            self.write(")");
31204        }
31205        Ok(())
31206    }
31207
31208    /// Generate an option expression without spaces around =
31209    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31210        match expr {
31211            Expression::Eq(eq) => {
31212                // Generate key=value without spaces
31213                self.generate_expression(&eq.left)?;
31214                self.write("=");
31215                self.generate_expression(&eq.right)?;
31216                Ok(())
31217            }
31218            _ => self.generate_expression(expr),
31219        }
31220    }
31221
31222    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31223        // Just output the name
31224        self.generate_expression(&e.this)?;
31225        Ok(())
31226    }
31227
31228    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31229        // PUT source_file @stage [options]
31230        self.write_keyword("PUT");
31231        self.write_space();
31232
31233        // Source file path - preserve original quoting
31234        if e.source_quoted {
31235            self.write("'");
31236            self.write(&e.source);
31237            self.write("'");
31238        } else {
31239            self.write(&e.source);
31240        }
31241
31242        self.write_space();
31243
31244        // Target stage reference - output the string directly (includes @)
31245        if let Expression::Literal(Literal::String(s)) = &e.target {
31246            self.write(s);
31247        } else {
31248            self.generate_expression(&e.target)?;
31249        }
31250
31251        // Optional parameters: KEY=VALUE
31252        for param in &e.params {
31253            self.write_space();
31254            self.write(&param.name);
31255            if let Some(ref value) = param.value {
31256                self.write("=");
31257                self.generate_expression(value)?;
31258            }
31259        }
31260
31261        Ok(())
31262    }
31263
31264    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31265        // QUANTILE(this, quantile)
31266        self.write_keyword("QUANTILE");
31267        self.write("(");
31268        self.generate_expression(&e.this)?;
31269        if let Some(quantile) = &e.quantile {
31270            self.write(", ");
31271            self.generate_expression(quantile)?;
31272        }
31273        self.write(")");
31274        Ok(())
31275    }
31276
31277    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31278        // QUERY_BAND = this [UPDATE] [FOR scope]
31279        if matches!(
31280            self.config.dialect,
31281            Some(crate::dialects::DialectType::Teradata)
31282        ) {
31283            self.write_keyword("SET");
31284            self.write_space();
31285        }
31286        self.write_keyword("QUERY_BAND");
31287        self.write(" = ");
31288        self.generate_expression(&e.this)?;
31289        if e.update.is_some() {
31290            self.write_space();
31291            self.write_keyword("UPDATE");
31292        }
31293        if let Some(scope) = &e.scope {
31294            self.write_space();
31295            self.write_keyword("FOR");
31296            self.write_space();
31297            self.generate_expression(scope)?;
31298        }
31299        Ok(())
31300    }
31301
31302    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31303        // this = expression
31304        self.generate_expression(&e.this)?;
31305        if let Some(expression) = &e.expression {
31306            self.write(" = ");
31307            self.generate_expression(expression)?;
31308        }
31309        Ok(())
31310    }
31311
31312    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31313        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31314        self.write_keyword("TRANSFORM");
31315        self.write("(");
31316        for (i, expr) in e.expressions.iter().enumerate() {
31317            if i > 0 {
31318                self.write(", ");
31319            }
31320            self.generate_expression(expr)?;
31321        }
31322        self.write(")");
31323        if let Some(row_format_before) = &e.row_format_before {
31324            self.write_space();
31325            self.generate_expression(row_format_before)?;
31326        }
31327        if let Some(record_writer) = &e.record_writer {
31328            self.write_space();
31329            self.write_keyword("RECORDWRITER");
31330            self.write_space();
31331            self.generate_expression(record_writer)?;
31332        }
31333        if let Some(command_script) = &e.command_script {
31334            self.write_space();
31335            self.write_keyword("USING");
31336            self.write_space();
31337            self.generate_expression(command_script)?;
31338        }
31339        if let Some(schema) = &e.schema {
31340            self.write_space();
31341            self.write_keyword("AS");
31342            self.write_space();
31343            self.generate_expression(schema)?;
31344        }
31345        if let Some(row_format_after) = &e.row_format_after {
31346            self.write_space();
31347            self.generate_expression(row_format_after)?;
31348        }
31349        if let Some(record_reader) = &e.record_reader {
31350            self.write_space();
31351            self.write_keyword("RECORDREADER");
31352            self.write_space();
31353            self.generate_expression(record_reader)?;
31354        }
31355        Ok(())
31356    }
31357
31358    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31359        // RANDN([seed])
31360        self.write_keyword("RANDN");
31361        self.write("(");
31362        if let Some(this) = &e.this {
31363            self.generate_expression(this)?;
31364        }
31365        self.write(")");
31366        Ok(())
31367    }
31368
31369    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31370        // RANDSTR(this, [generator])
31371        self.write_keyword("RANDSTR");
31372        self.write("(");
31373        self.generate_expression(&e.this)?;
31374        if let Some(generator) = &e.generator {
31375            self.write(", ");
31376            self.generate_expression(generator)?;
31377        }
31378        self.write(")");
31379        Ok(())
31380    }
31381
31382    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31383        // RANGE_BUCKET(this, expression)
31384        self.write_keyword("RANGE_BUCKET");
31385        self.write("(");
31386        self.generate_expression(&e.this)?;
31387        self.write(", ");
31388        self.generate_expression(&e.expression)?;
31389        self.write(")");
31390        Ok(())
31391    }
31392
31393    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31394        // RANGE_N(this BETWEEN expressions [EACH each])
31395        self.write_keyword("RANGE_N");
31396        self.write("(");
31397        self.generate_expression(&e.this)?;
31398        self.write_space();
31399        self.write_keyword("BETWEEN");
31400        self.write_space();
31401        for (i, expr) in e.expressions.iter().enumerate() {
31402            if i > 0 {
31403                self.write(", ");
31404            }
31405            self.generate_expression(expr)?;
31406        }
31407        if let Some(each) = &e.each {
31408            self.write_space();
31409            self.write_keyword("EACH");
31410            self.write_space();
31411            self.generate_expression(each)?;
31412        }
31413        self.write(")");
31414        Ok(())
31415    }
31416
31417    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31418        // READ_CSV(this, expressions...)
31419        self.write_keyword("READ_CSV");
31420        self.write("(");
31421        self.generate_expression(&e.this)?;
31422        for expr in &e.expressions {
31423            self.write(", ");
31424            self.generate_expression(expr)?;
31425        }
31426        self.write(")");
31427        Ok(())
31428    }
31429
31430    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31431        // READ_PARQUET(expressions...)
31432        self.write_keyword("READ_PARQUET");
31433        self.write("(");
31434        for (i, expr) in e.expressions.iter().enumerate() {
31435            if i > 0 {
31436                self.write(", ");
31437            }
31438            self.generate_expression(expr)?;
31439        }
31440        self.write(")");
31441        Ok(())
31442    }
31443
31444    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31445        // SEARCH kind FIRST BY this SET expression [USING using]
31446        // or CYCLE this SET expression [USING using]
31447        if e.kind == "CYCLE" {
31448            self.write_keyword("CYCLE");
31449        } else {
31450            self.write_keyword("SEARCH");
31451            self.write_space();
31452            self.write(&e.kind);
31453            self.write_space();
31454            self.write_keyword("FIRST BY");
31455        }
31456        self.write_space();
31457        self.generate_expression(&e.this)?;
31458        self.write_space();
31459        self.write_keyword("SET");
31460        self.write_space();
31461        self.generate_expression(&e.expression)?;
31462        if let Some(using) = &e.using {
31463            self.write_space();
31464            self.write_keyword("USING");
31465            self.write_space();
31466            self.generate_expression(using)?;
31467        }
31468        Ok(())
31469    }
31470
31471    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31472        // REDUCE(this, initial, merge, [finish])
31473        self.write_keyword("REDUCE");
31474        self.write("(");
31475        self.generate_expression(&e.this)?;
31476        if let Some(initial) = &e.initial {
31477            self.write(", ");
31478            self.generate_expression(initial)?;
31479        }
31480        if let Some(merge) = &e.merge {
31481            self.write(", ");
31482            self.generate_expression(merge)?;
31483        }
31484        if let Some(finish) = &e.finish {
31485            self.write(", ");
31486            self.generate_expression(finish)?;
31487        }
31488        self.write(")");
31489        Ok(())
31490    }
31491
31492    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31493        // REFERENCES this (expressions) [options]
31494        self.write_keyword("REFERENCES");
31495        self.write_space();
31496        self.generate_expression(&e.this)?;
31497        if !e.expressions.is_empty() {
31498            self.write(" (");
31499            for (i, expr) in e.expressions.iter().enumerate() {
31500                if i > 0 {
31501                    self.write(", ");
31502                }
31503                self.generate_expression(expr)?;
31504            }
31505            self.write(")");
31506        }
31507        for opt in &e.options {
31508            self.write_space();
31509            self.generate_expression(opt)?;
31510        }
31511        Ok(())
31512    }
31513
31514    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31515        // REFRESH [kind] this
31516        self.write_keyword("REFRESH");
31517        if !e.kind.is_empty() {
31518            self.write_space();
31519            self.write_keyword(&e.kind);
31520        }
31521        self.write_space();
31522        self.generate_expression(&e.this)?;
31523        Ok(())
31524    }
31525
31526    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31527        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31528        self.write_keyword("REFRESH");
31529        self.write_space();
31530        self.write_keyword(&e.method);
31531
31532        if let Some(ref kind) = e.kind {
31533            self.write_space();
31534            self.write_keyword("ON");
31535            self.write_space();
31536            self.write_keyword(kind);
31537
31538            // EVERY n UNIT
31539            if let Some(ref every) = e.every {
31540                self.write_space();
31541                self.write_keyword("EVERY");
31542                self.write_space();
31543                self.generate_expression(every)?;
31544                if let Some(ref unit) = e.unit {
31545                    self.write_space();
31546                    self.write_keyword(unit);
31547                }
31548            }
31549
31550            // STARTS 'datetime'
31551            if let Some(ref starts) = e.starts {
31552                self.write_space();
31553                self.write_keyword("STARTS");
31554                self.write_space();
31555                self.generate_expression(starts)?;
31556            }
31557        }
31558        Ok(())
31559    }
31560
31561    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31562        // REGEXP_COUNT(this, expression, position, parameters)
31563        self.write_keyword("REGEXP_COUNT");
31564        self.write("(");
31565        self.generate_expression(&e.this)?;
31566        self.write(", ");
31567        self.generate_expression(&e.expression)?;
31568        if let Some(position) = &e.position {
31569            self.write(", ");
31570            self.generate_expression(position)?;
31571        }
31572        if let Some(parameters) = &e.parameters {
31573            self.write(", ");
31574            self.generate_expression(parameters)?;
31575        }
31576        self.write(")");
31577        Ok(())
31578    }
31579
31580    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31581        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31582        self.write_keyword("REGEXP_EXTRACT_ALL");
31583        self.write("(");
31584        self.generate_expression(&e.this)?;
31585        self.write(", ");
31586        self.generate_expression(&e.expression)?;
31587        if let Some(group) = &e.group {
31588            self.write(", ");
31589            self.generate_expression(group)?;
31590        }
31591        self.write(")");
31592        Ok(())
31593    }
31594
31595    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31596        // REGEXP_FULL_MATCH(this, expression)
31597        self.write_keyword("REGEXP_FULL_MATCH");
31598        self.write("(");
31599        self.generate_expression(&e.this)?;
31600        self.write(", ");
31601        self.generate_expression(&e.expression)?;
31602        self.write(")");
31603        Ok(())
31604    }
31605
31606    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31607        use crate::dialects::DialectType;
31608        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31609        if matches!(
31610            self.config.dialect,
31611            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31612        ) && e.flag.is_none()
31613        {
31614            self.generate_expression(&e.this)?;
31615            self.write(" ~* ");
31616            self.generate_expression(&e.expression)?;
31617        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31618            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31619            self.write_keyword("REGEXP_LIKE");
31620            self.write("(");
31621            self.generate_expression(&e.this)?;
31622            self.write(", ");
31623            self.generate_expression(&e.expression)?;
31624            self.write(", ");
31625            if let Some(flag) = &e.flag {
31626                self.generate_expression(flag)?;
31627            } else {
31628                self.write("'i'");
31629            }
31630            self.write(")");
31631        } else {
31632            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31633            self.generate_expression(&e.this)?;
31634            self.write_space();
31635            self.write_keyword("REGEXP_ILIKE");
31636            self.write_space();
31637            self.generate_expression(&e.expression)?;
31638            if let Some(flag) = &e.flag {
31639                self.write(", ");
31640                self.generate_expression(flag)?;
31641            }
31642        }
31643        Ok(())
31644    }
31645
31646    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31647        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31648        self.write_keyword("REGEXP_INSTR");
31649        self.write("(");
31650        self.generate_expression(&e.this)?;
31651        self.write(", ");
31652        self.generate_expression(&e.expression)?;
31653        if let Some(position) = &e.position {
31654            self.write(", ");
31655            self.generate_expression(position)?;
31656        }
31657        if let Some(occurrence) = &e.occurrence {
31658            self.write(", ");
31659            self.generate_expression(occurrence)?;
31660        }
31661        if let Some(option) = &e.option {
31662            self.write(", ");
31663            self.generate_expression(option)?;
31664        }
31665        if let Some(parameters) = &e.parameters {
31666            self.write(", ");
31667            self.generate_expression(parameters)?;
31668        }
31669        if let Some(group) = &e.group {
31670            self.write(", ");
31671            self.generate_expression(group)?;
31672        }
31673        self.write(")");
31674        Ok(())
31675    }
31676
31677    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31678        // REGEXP_SPLIT(this, expression, limit)
31679        self.write_keyword("REGEXP_SPLIT");
31680        self.write("(");
31681        self.generate_expression(&e.this)?;
31682        self.write(", ");
31683        self.generate_expression(&e.expression)?;
31684        if let Some(limit) = &e.limit {
31685            self.write(", ");
31686            self.generate_expression(limit)?;
31687        }
31688        self.write(")");
31689        Ok(())
31690    }
31691
31692    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31693        // REGR_AVGX(this, expression)
31694        self.write_keyword("REGR_AVGX");
31695        self.write("(");
31696        self.generate_expression(&e.this)?;
31697        self.write(", ");
31698        self.generate_expression(&e.expression)?;
31699        self.write(")");
31700        Ok(())
31701    }
31702
31703    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31704        // REGR_AVGY(this, expression)
31705        self.write_keyword("REGR_AVGY");
31706        self.write("(");
31707        self.generate_expression(&e.this)?;
31708        self.write(", ");
31709        self.generate_expression(&e.expression)?;
31710        self.write(")");
31711        Ok(())
31712    }
31713
31714    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31715        // REGR_COUNT(this, expression)
31716        self.write_keyword("REGR_COUNT");
31717        self.write("(");
31718        self.generate_expression(&e.this)?;
31719        self.write(", ");
31720        self.generate_expression(&e.expression)?;
31721        self.write(")");
31722        Ok(())
31723    }
31724
31725    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31726        // REGR_INTERCEPT(this, expression)
31727        self.write_keyword("REGR_INTERCEPT");
31728        self.write("(");
31729        self.generate_expression(&e.this)?;
31730        self.write(", ");
31731        self.generate_expression(&e.expression)?;
31732        self.write(")");
31733        Ok(())
31734    }
31735
31736    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31737        // REGR_R2(this, expression)
31738        self.write_keyword("REGR_R2");
31739        self.write("(");
31740        self.generate_expression(&e.this)?;
31741        self.write(", ");
31742        self.generate_expression(&e.expression)?;
31743        self.write(")");
31744        Ok(())
31745    }
31746
31747    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31748        // REGR_SLOPE(this, expression)
31749        self.write_keyword("REGR_SLOPE");
31750        self.write("(");
31751        self.generate_expression(&e.this)?;
31752        self.write(", ");
31753        self.generate_expression(&e.expression)?;
31754        self.write(")");
31755        Ok(())
31756    }
31757
31758    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31759        // REGR_SXX(this, expression)
31760        self.write_keyword("REGR_SXX");
31761        self.write("(");
31762        self.generate_expression(&e.this)?;
31763        self.write(", ");
31764        self.generate_expression(&e.expression)?;
31765        self.write(")");
31766        Ok(())
31767    }
31768
31769    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31770        // REGR_SXY(this, expression)
31771        self.write_keyword("REGR_SXY");
31772        self.write("(");
31773        self.generate_expression(&e.this)?;
31774        self.write(", ");
31775        self.generate_expression(&e.expression)?;
31776        self.write(")");
31777        Ok(())
31778    }
31779
31780    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31781        // REGR_SYY(this, expression)
31782        self.write_keyword("REGR_SYY");
31783        self.write("(");
31784        self.generate_expression(&e.this)?;
31785        self.write(", ");
31786        self.generate_expression(&e.expression)?;
31787        self.write(")");
31788        Ok(())
31789    }
31790
31791    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31792        // REGR_VALX(this, expression)
31793        self.write_keyword("REGR_VALX");
31794        self.write("(");
31795        self.generate_expression(&e.this)?;
31796        self.write(", ");
31797        self.generate_expression(&e.expression)?;
31798        self.write(")");
31799        Ok(())
31800    }
31801
31802    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31803        // REGR_VALY(this, expression)
31804        self.write_keyword("REGR_VALY");
31805        self.write("(");
31806        self.generate_expression(&e.this)?;
31807        self.write(", ");
31808        self.generate_expression(&e.expression)?;
31809        self.write(")");
31810        Ok(())
31811    }
31812
31813    fn generate_remote_with_connection_model_property(
31814        &mut self,
31815        e: &RemoteWithConnectionModelProperty,
31816    ) -> Result<()> {
31817        // REMOTE WITH CONNECTION this
31818        self.write_keyword("REMOTE WITH CONNECTION");
31819        self.write_space();
31820        self.generate_expression(&e.this)?;
31821        Ok(())
31822    }
31823
31824    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31825        // RENAME COLUMN [IF EXISTS] this TO new_name
31826        self.write_keyword("RENAME COLUMN");
31827        if e.exists {
31828            self.write_space();
31829            self.write_keyword("IF EXISTS");
31830        }
31831        self.write_space();
31832        self.generate_expression(&e.this)?;
31833        if let Some(to) = &e.to {
31834            self.write_space();
31835            self.write_keyword("TO");
31836            self.write_space();
31837            self.generate_expression(to)?;
31838        }
31839        Ok(())
31840    }
31841
31842    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31843        // REPLACE PARTITION expression [FROM source]
31844        self.write_keyword("REPLACE PARTITION");
31845        self.write_space();
31846        self.generate_expression(&e.expression)?;
31847        if let Some(source) = &e.source {
31848            self.write_space();
31849            self.write_keyword("FROM");
31850            self.write_space();
31851            self.generate_expression(source)?;
31852        }
31853        Ok(())
31854    }
31855
31856    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31857        // RETURNING expressions [INTO into]
31858        // TSQL and Fabric use OUTPUT instead of RETURNING
31859        let keyword = match self.config.dialect {
31860            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31861            _ => "RETURNING",
31862        };
31863        self.write_keyword(keyword);
31864        self.write_space();
31865        for (i, expr) in e.expressions.iter().enumerate() {
31866            if i > 0 {
31867                self.write(", ");
31868            }
31869            self.generate_expression(expr)?;
31870        }
31871        if let Some(into) = &e.into {
31872            self.write_space();
31873            self.write_keyword("INTO");
31874            self.write_space();
31875            self.generate_expression(into)?;
31876        }
31877        Ok(())
31878    }
31879
31880    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31881        // OUTPUT expressions [INTO into_table]
31882        self.write_space();
31883        self.write_keyword("OUTPUT");
31884        self.write_space();
31885        for (i, expr) in output.columns.iter().enumerate() {
31886            if i > 0 {
31887                self.write(", ");
31888            }
31889            self.generate_expression(expr)?;
31890        }
31891        if let Some(into_table) = &output.into_table {
31892            self.write_space();
31893            self.write_keyword("INTO");
31894            self.write_space();
31895            self.generate_expression(into_table)?;
31896        }
31897        Ok(())
31898    }
31899
31900    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31901        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31902        self.write_keyword("RETURNS");
31903        if e.is_table.is_some() {
31904            self.write_space();
31905            self.write_keyword("TABLE");
31906        }
31907        if let Some(table) = &e.table {
31908            self.write_space();
31909            self.generate_expression(table)?;
31910        } else if let Some(this) = &e.this {
31911            self.write_space();
31912            self.generate_expression(this)?;
31913        }
31914        if e.null.is_some() {
31915            self.write_space();
31916            self.write_keyword("NULL ON NULL INPUT");
31917        }
31918        Ok(())
31919    }
31920
31921    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31922        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31923        self.write_keyword("ROLLBACK");
31924
31925        // TSQL always uses ROLLBACK TRANSACTION
31926        if e.this.is_none()
31927            && matches!(
31928                self.config.dialect,
31929                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31930            )
31931        {
31932            self.write_space();
31933            self.write_keyword("TRANSACTION");
31934        }
31935
31936        // Check if this has TRANSACTION keyword or transaction name
31937        if let Some(this) = &e.this {
31938            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31939            let is_transaction_marker = matches!(
31940                this.as_ref(),
31941                Expression::Identifier(id) if id.name == "TRANSACTION"
31942            );
31943
31944            self.write_space();
31945            self.write_keyword("TRANSACTION");
31946
31947            // If it's a real transaction name, output it
31948            if !is_transaction_marker {
31949                self.write_space();
31950                self.generate_expression(this)?;
31951            }
31952        }
31953
31954        // Output TO savepoint
31955        if let Some(savepoint) = &e.savepoint {
31956            self.write_space();
31957            self.write_keyword("TO");
31958            self.write_space();
31959            self.generate_expression(savepoint)?;
31960        }
31961        Ok(())
31962    }
31963
31964    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31965        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31966        if e.expressions.is_empty() {
31967            self.write_keyword("WITH ROLLUP");
31968        } else {
31969            self.write_keyword("ROLLUP");
31970            self.write("(");
31971            for (i, expr) in e.expressions.iter().enumerate() {
31972                if i > 0 {
31973                    self.write(", ");
31974                }
31975                self.generate_expression(expr)?;
31976            }
31977            self.write(")");
31978        }
31979        Ok(())
31980    }
31981
31982    fn generate_row_format_delimited_property(
31983        &mut self,
31984        e: &RowFormatDelimitedProperty,
31985    ) -> Result<()> {
31986        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31987        self.write_keyword("ROW FORMAT DELIMITED");
31988        if let Some(fields) = &e.fields {
31989            self.write_space();
31990            self.write_keyword("FIELDS TERMINATED BY");
31991            self.write_space();
31992            self.generate_expression(fields)?;
31993        }
31994        if let Some(escaped) = &e.escaped {
31995            self.write_space();
31996            self.write_keyword("ESCAPED BY");
31997            self.write_space();
31998            self.generate_expression(escaped)?;
31999        }
32000        if let Some(items) = &e.collection_items {
32001            self.write_space();
32002            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
32003            self.write_space();
32004            self.generate_expression(items)?;
32005        }
32006        if let Some(keys) = &e.map_keys {
32007            self.write_space();
32008            self.write_keyword("MAP KEYS TERMINATED BY");
32009            self.write_space();
32010            self.generate_expression(keys)?;
32011        }
32012        if let Some(lines) = &e.lines {
32013            self.write_space();
32014            self.write_keyword("LINES TERMINATED BY");
32015            self.write_space();
32016            self.generate_expression(lines)?;
32017        }
32018        if let Some(null) = &e.null {
32019            self.write_space();
32020            self.write_keyword("NULL DEFINED AS");
32021            self.write_space();
32022            self.generate_expression(null)?;
32023        }
32024        if let Some(serde) = &e.serde {
32025            self.write_space();
32026            self.generate_expression(serde)?;
32027        }
32028        Ok(())
32029    }
32030
32031    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
32032        // ROW FORMAT this
32033        self.write_keyword("ROW FORMAT");
32034        self.write_space();
32035        self.generate_expression(&e.this)?;
32036        Ok(())
32037    }
32038
32039    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
32040        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
32041        self.write_keyword("ROW FORMAT SERDE");
32042        self.write_space();
32043        self.generate_expression(&e.this)?;
32044        if let Some(props) = &e.serde_properties {
32045            self.write_space();
32046            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
32047            self.generate_expression(props)?;
32048        }
32049        Ok(())
32050    }
32051
32052    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
32053        // SHA2(this, length)
32054        self.write_keyword("SHA2");
32055        self.write("(");
32056        self.generate_expression(&e.this)?;
32057        if let Some(length) = e.length {
32058            self.write(", ");
32059            self.write(&length.to_string());
32060        }
32061        self.write(")");
32062        Ok(())
32063    }
32064
32065    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
32066        // SHA2_DIGEST(this, length)
32067        self.write_keyword("SHA2_DIGEST");
32068        self.write("(");
32069        self.generate_expression(&e.this)?;
32070        if let Some(length) = e.length {
32071            self.write(", ");
32072            self.write(&length.to_string());
32073        }
32074        self.write(")");
32075        Ok(())
32076    }
32077
32078    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
32079        let name = if matches!(
32080            self.config.dialect,
32081            Some(crate::dialects::DialectType::Spark)
32082                | Some(crate::dialects::DialectType::Databricks)
32083        ) {
32084            "TRY_ADD"
32085        } else {
32086            "SAFE_ADD"
32087        };
32088        self.write_keyword(name);
32089        self.write("(");
32090        self.generate_expression(&e.this)?;
32091        self.write(", ");
32092        self.generate_expression(&e.expression)?;
32093        self.write(")");
32094        Ok(())
32095    }
32096
32097    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
32098        // SAFE_DIVIDE(this, expression)
32099        self.write_keyword("SAFE_DIVIDE");
32100        self.write("(");
32101        self.generate_expression(&e.this)?;
32102        self.write(", ");
32103        self.generate_expression(&e.expression)?;
32104        self.write(")");
32105        Ok(())
32106    }
32107
32108    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
32109        let name = if matches!(
32110            self.config.dialect,
32111            Some(crate::dialects::DialectType::Spark)
32112                | Some(crate::dialects::DialectType::Databricks)
32113        ) {
32114            "TRY_MULTIPLY"
32115        } else {
32116            "SAFE_MULTIPLY"
32117        };
32118        self.write_keyword(name);
32119        self.write("(");
32120        self.generate_expression(&e.this)?;
32121        self.write(", ");
32122        self.generate_expression(&e.expression)?;
32123        self.write(")");
32124        Ok(())
32125    }
32126
32127    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
32128        let name = if matches!(
32129            self.config.dialect,
32130            Some(crate::dialects::DialectType::Spark)
32131                | Some(crate::dialects::DialectType::Databricks)
32132        ) {
32133            "TRY_SUBTRACT"
32134        } else {
32135            "SAFE_SUBTRACT"
32136        };
32137        self.write_keyword(name);
32138        self.write("(");
32139        self.generate_expression(&e.this)?;
32140        self.write(", ");
32141        self.generate_expression(&e.expression)?;
32142        self.write(")");
32143        Ok(())
32144    }
32145
32146    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32147    /// METHOD (size UNIT) [REPEATABLE (seed)]
32148    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32149        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32150        if matches!(sample.method, SampleMethod::Bucket) {
32151            self.write(" (");
32152            self.write_keyword("BUCKET");
32153            self.write_space();
32154            if let Some(ref num) = sample.bucket_numerator {
32155                self.generate_expression(num)?;
32156            }
32157            self.write_space();
32158            self.write_keyword("OUT OF");
32159            self.write_space();
32160            if let Some(ref denom) = sample.bucket_denominator {
32161                self.generate_expression(denom)?;
32162            }
32163            if let Some(ref field) = sample.bucket_field {
32164                self.write_space();
32165                self.write_keyword("ON");
32166                self.write_space();
32167                self.generate_expression(field)?;
32168            }
32169            self.write(")");
32170            return Ok(());
32171        }
32172
32173        // Output method name if explicitly specified, or for dialects that always require it
32174        let is_snowflake = matches!(
32175            self.config.dialect,
32176            Some(crate::dialects::DialectType::Snowflake)
32177        );
32178        let is_postgres = matches!(
32179            self.config.dialect,
32180            Some(crate::dialects::DialectType::PostgreSQL)
32181                | Some(crate::dialects::DialectType::Redshift)
32182        );
32183        // Databricks and Spark don't output method names
32184        let is_databricks = matches!(
32185            self.config.dialect,
32186            Some(crate::dialects::DialectType::Databricks)
32187        );
32188        let is_spark = matches!(
32189            self.config.dialect,
32190            Some(crate::dialects::DialectType::Spark)
32191        );
32192        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32193        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32194        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32195        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32196            self.write_space();
32197            if !sample.explicit_method && (is_snowflake || force_method) {
32198                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32199                self.write_keyword("BERNOULLI");
32200            } else {
32201                match sample.method {
32202                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32203                    SampleMethod::System => self.write_keyword("SYSTEM"),
32204                    SampleMethod::Block => self.write_keyword("BLOCK"),
32205                    SampleMethod::Row => self.write_keyword("ROW"),
32206                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32207                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32208                    SampleMethod::Bucket => {} // handled above
32209                }
32210            }
32211        }
32212
32213        // Output size, with or without parentheses depending on dialect
32214        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32215        if emit_size_no_parens {
32216            self.write_space();
32217            match &sample.size {
32218                Expression::Tuple(tuple) => {
32219                    for (i, expr) in tuple.expressions.iter().enumerate() {
32220                        if i > 0 {
32221                            self.write(", ");
32222                        }
32223                        self.generate_expression(expr)?;
32224                    }
32225                }
32226                expr => self.generate_expression(expr)?,
32227            }
32228        } else {
32229            self.write(" (");
32230            self.generate_expression(&sample.size)?;
32231        }
32232
32233        // Determine unit
32234        let is_rows_method = matches!(
32235            sample.method,
32236            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32237        );
32238        let is_percent = matches!(
32239            sample.method,
32240            SampleMethod::Percent
32241                | SampleMethod::System
32242                | SampleMethod::Bernoulli
32243                | SampleMethod::Block
32244        );
32245
32246        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32247        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32248        // For Databricks and Spark, always output PERCENT for percentage samples.
32249        let is_presto = matches!(
32250            self.config.dialect,
32251            Some(crate::dialects::DialectType::Presto)
32252                | Some(crate::dialects::DialectType::Trino)
32253                | Some(crate::dialects::DialectType::Athena)
32254        );
32255        let should_output_unit = if is_databricks || is_spark {
32256            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32257            is_percent || is_rows_method || sample.unit_after_size
32258        } else if is_snowflake || is_postgres || is_presto {
32259            sample.unit_after_size
32260        } else {
32261            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32262        };
32263
32264        if should_output_unit {
32265            self.write_space();
32266            if sample.is_percent {
32267                self.write_keyword("PERCENT");
32268            } else if is_rows_method && !sample.unit_after_size {
32269                self.write_keyword("ROWS");
32270            } else if sample.unit_after_size {
32271                match sample.method {
32272                    SampleMethod::Percent
32273                    | SampleMethod::System
32274                    | SampleMethod::Bernoulli
32275                    | SampleMethod::Block => {
32276                        self.write_keyword("PERCENT");
32277                    }
32278                    SampleMethod::Row | SampleMethod::Reservoir => {
32279                        self.write_keyword("ROWS");
32280                    }
32281                    _ => self.write_keyword("ROWS"),
32282                }
32283            } else {
32284                self.write_keyword("PERCENT");
32285            }
32286        }
32287
32288        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32289            if let Some(ref offset) = sample.offset {
32290                self.write_space();
32291                self.write_keyword("OFFSET");
32292                self.write_space();
32293                self.generate_expression(offset)?;
32294            }
32295        }
32296        if !emit_size_no_parens {
32297            self.write(")");
32298        }
32299
32300        Ok(())
32301    }
32302
32303    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32304        // SAMPLE this (ClickHouse uses SAMPLE BY)
32305        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32306            self.write_keyword("SAMPLE BY");
32307        } else {
32308            self.write_keyword("SAMPLE");
32309        }
32310        self.write_space();
32311        self.generate_expression(&e.this)?;
32312        Ok(())
32313    }
32314
32315    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32316        // this (expressions...)
32317        if let Some(this) = &e.this {
32318            self.generate_expression(this)?;
32319        }
32320        if !e.expressions.is_empty() {
32321            // Add space before column list if there's a preceding expression
32322            if e.this.is_some() {
32323                self.write_space();
32324            }
32325            self.write("(");
32326            for (i, expr) in e.expressions.iter().enumerate() {
32327                if i > 0 {
32328                    self.write(", ");
32329                }
32330                self.generate_expression(expr)?;
32331            }
32332            self.write(")");
32333        }
32334        Ok(())
32335    }
32336
32337    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32338        // COMMENT this
32339        self.write_keyword("COMMENT");
32340        self.write_space();
32341        self.generate_expression(&e.this)?;
32342        Ok(())
32343    }
32344
32345    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32346        // [this::]expression
32347        if let Some(this) = &e.this {
32348            self.generate_expression(this)?;
32349            self.write("::");
32350        }
32351        self.generate_expression(&e.expression)?;
32352        Ok(())
32353    }
32354
32355    fn generate_search(&mut self, e: &Search) -> Result<()> {
32356        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32357        self.write_keyword("SEARCH");
32358        self.write("(");
32359        self.generate_expression(&e.this)?;
32360        self.write(", ");
32361        self.generate_expression(&e.expression)?;
32362        if let Some(json_scope) = &e.json_scope {
32363            self.write(", ");
32364            self.generate_expression(json_scope)?;
32365        }
32366        if let Some(analyzer) = &e.analyzer {
32367            self.write(", ");
32368            self.generate_expression(analyzer)?;
32369        }
32370        if let Some(analyzer_options) = &e.analyzer_options {
32371            self.write(", ");
32372            self.generate_expression(analyzer_options)?;
32373        }
32374        if let Some(search_mode) = &e.search_mode {
32375            self.write(", ");
32376            self.generate_expression(search_mode)?;
32377        }
32378        self.write(")");
32379        Ok(())
32380    }
32381
32382    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32383        // SEARCH_IP(this, expression)
32384        self.write_keyword("SEARCH_IP");
32385        self.write("(");
32386        self.generate_expression(&e.this)?;
32387        self.write(", ");
32388        self.generate_expression(&e.expression)?;
32389        self.write(")");
32390        Ok(())
32391    }
32392
32393    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32394        // SECURITY this
32395        self.write_keyword("SECURITY");
32396        self.write_space();
32397        self.generate_expression(&e.this)?;
32398        Ok(())
32399    }
32400
32401    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32402        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32403        self.write("SEMANTIC_VIEW(");
32404
32405        if self.config.pretty {
32406            // Pretty print: each clause on its own line
32407            self.write_newline();
32408            self.indent_level += 1;
32409            self.write_indent();
32410            self.generate_expression(&e.this)?;
32411
32412            if let Some(metrics) = &e.metrics {
32413                self.write_newline();
32414                self.write_indent();
32415                self.write_keyword("METRICS");
32416                self.write_space();
32417                self.generate_semantic_view_tuple(metrics)?;
32418            }
32419            if let Some(dimensions) = &e.dimensions {
32420                self.write_newline();
32421                self.write_indent();
32422                self.write_keyword("DIMENSIONS");
32423                self.write_space();
32424                self.generate_semantic_view_tuple(dimensions)?;
32425            }
32426            if let Some(facts) = &e.facts {
32427                self.write_newline();
32428                self.write_indent();
32429                self.write_keyword("FACTS");
32430                self.write_space();
32431                self.generate_semantic_view_tuple(facts)?;
32432            }
32433            if let Some(where_) = &e.where_ {
32434                self.write_newline();
32435                self.write_indent();
32436                self.write_keyword("WHERE");
32437                self.write_space();
32438                self.generate_expression(where_)?;
32439            }
32440            self.write_newline();
32441            self.indent_level -= 1;
32442            self.write_indent();
32443        } else {
32444            // Compact: all on one line
32445            self.generate_expression(&e.this)?;
32446            if let Some(metrics) = &e.metrics {
32447                self.write_space();
32448                self.write_keyword("METRICS");
32449                self.write_space();
32450                self.generate_semantic_view_tuple(metrics)?;
32451            }
32452            if let Some(dimensions) = &e.dimensions {
32453                self.write_space();
32454                self.write_keyword("DIMENSIONS");
32455                self.write_space();
32456                self.generate_semantic_view_tuple(dimensions)?;
32457            }
32458            if let Some(facts) = &e.facts {
32459                self.write_space();
32460                self.write_keyword("FACTS");
32461                self.write_space();
32462                self.generate_semantic_view_tuple(facts)?;
32463            }
32464            if let Some(where_) = &e.where_ {
32465                self.write_space();
32466                self.write_keyword("WHERE");
32467                self.write_space();
32468                self.generate_expression(where_)?;
32469            }
32470        }
32471        self.write(")");
32472        Ok(())
32473    }
32474
32475    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32476    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32477        if let Expression::Tuple(t) = expr {
32478            for (i, e) in t.expressions.iter().enumerate() {
32479                if i > 0 {
32480                    self.write(", ");
32481                }
32482                self.generate_expression(e)?;
32483            }
32484        } else {
32485            self.generate_expression(expr)?;
32486        }
32487        Ok(())
32488    }
32489
32490    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32491        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32492        if let Some(start) = &e.start {
32493            self.write_keyword("START WITH");
32494            self.write_space();
32495            self.generate_expression(start)?;
32496        }
32497        if let Some(increment) = &e.increment {
32498            self.write_space();
32499            self.write_keyword("INCREMENT BY");
32500            self.write_space();
32501            self.generate_expression(increment)?;
32502        }
32503        if let Some(minvalue) = &e.minvalue {
32504            self.write_space();
32505            self.write_keyword("MINVALUE");
32506            self.write_space();
32507            self.generate_expression(minvalue)?;
32508        }
32509        if let Some(maxvalue) = &e.maxvalue {
32510            self.write_space();
32511            self.write_keyword("MAXVALUE");
32512            self.write_space();
32513            self.generate_expression(maxvalue)?;
32514        }
32515        if let Some(cache) = &e.cache {
32516            self.write_space();
32517            self.write_keyword("CACHE");
32518            self.write_space();
32519            self.generate_expression(cache)?;
32520        }
32521        if let Some(owned) = &e.owned {
32522            self.write_space();
32523            self.write_keyword("OWNED BY");
32524            self.write_space();
32525            self.generate_expression(owned)?;
32526        }
32527        for opt in &e.options {
32528            self.write_space();
32529            self.generate_expression(opt)?;
32530        }
32531        Ok(())
32532    }
32533
32534    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32535        // [WITH] SERDEPROPERTIES (expressions)
32536        if e.with_.is_some() {
32537            self.write_keyword("WITH");
32538            self.write_space();
32539        }
32540        self.write_keyword("SERDEPROPERTIES");
32541        self.write(" (");
32542        for (i, expr) in e.expressions.iter().enumerate() {
32543            if i > 0 {
32544                self.write(", ");
32545            }
32546            // Generate key=value without spaces around =
32547            match expr {
32548                Expression::Eq(eq) => {
32549                    self.generate_expression(&eq.left)?;
32550                    self.write("=");
32551                    self.generate_expression(&eq.right)?;
32552                }
32553                _ => self.generate_expression(expr)?,
32554            }
32555        }
32556        self.write(")");
32557        Ok(())
32558    }
32559
32560    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32561        // @@[kind.]this
32562        self.write("@@");
32563        if let Some(kind) = &e.kind {
32564            self.write(kind);
32565            self.write(".");
32566        }
32567        self.generate_expression(&e.this)?;
32568        Ok(())
32569    }
32570
32571    fn generate_set(&mut self, e: &Set) -> Result<()> {
32572        // SET/UNSET [TAG] expressions
32573        if e.unset.is_some() {
32574            self.write_keyword("UNSET");
32575        } else {
32576            self.write_keyword("SET");
32577        }
32578        if e.tag.is_some() {
32579            self.write_space();
32580            self.write_keyword("TAG");
32581        }
32582        if !e.expressions.is_empty() {
32583            self.write_space();
32584            for (i, expr) in e.expressions.iter().enumerate() {
32585                if i > 0 {
32586                    self.write(", ");
32587                }
32588                self.generate_expression(expr)?;
32589            }
32590        }
32591        Ok(())
32592    }
32593
32594    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32595        // SET this or SETCONFIG this
32596        self.write_keyword("SET");
32597        self.write_space();
32598        self.generate_expression(&e.this)?;
32599        Ok(())
32600    }
32601
32602    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32603        // [kind] name = value
32604        if let Some(kind) = &e.kind {
32605            self.write_keyword(kind);
32606            self.write_space();
32607        }
32608        self.generate_expression(&e.name)?;
32609        self.write(" = ");
32610        self.generate_expression(&e.value)?;
32611        Ok(())
32612    }
32613
32614    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32615        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32616        if let Some(with_) = &e.with_ {
32617            self.generate_expression(with_)?;
32618            self.write_space();
32619        }
32620        self.generate_expression(&e.this)?;
32621        self.write_space();
32622        // kind should be UNION, INTERSECT, EXCEPT, etc.
32623        if let Some(kind) = &e.kind {
32624            self.write_keyword(kind);
32625        }
32626        if e.distinct {
32627            self.write_space();
32628            self.write_keyword("DISTINCT");
32629        } else {
32630            self.write_space();
32631            self.write_keyword("ALL");
32632        }
32633        if e.by_name.is_some() {
32634            self.write_space();
32635            self.write_keyword("BY NAME");
32636        }
32637        self.write_space();
32638        self.generate_expression(&e.expression)?;
32639        Ok(())
32640    }
32641
32642    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32643        // SET or MULTISET
32644        if e.multi.is_some() {
32645            self.write_keyword("MULTISET");
32646        } else {
32647            self.write_keyword("SET");
32648        }
32649        Ok(())
32650    }
32651
32652    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32653        // SETTINGS expressions
32654        self.write_keyword("SETTINGS");
32655        if self.config.pretty && e.expressions.len() > 1 {
32656            // Pretty print: each setting on its own line, indented
32657            self.indent_level += 1;
32658            for (i, expr) in e.expressions.iter().enumerate() {
32659                if i > 0 {
32660                    self.write(",");
32661                }
32662                self.write_newline();
32663                self.write_indent();
32664                self.generate_expression(expr)?;
32665            }
32666            self.indent_level -= 1;
32667        } else {
32668            self.write_space();
32669            for (i, expr) in e.expressions.iter().enumerate() {
32670                if i > 0 {
32671                    self.write(", ");
32672                }
32673                self.generate_expression(expr)?;
32674            }
32675        }
32676        Ok(())
32677    }
32678
32679    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32680        // SHARING = this
32681        self.write_keyword("SHARING");
32682        if let Some(this) = &e.this {
32683            self.write(" = ");
32684            self.generate_expression(this)?;
32685        }
32686        Ok(())
32687    }
32688
32689    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32690        // Python array slicing: begin:end:step
32691        if let Some(begin) = &e.this {
32692            self.generate_expression(begin)?;
32693        }
32694        self.write(":");
32695        if let Some(end) = &e.expression {
32696            self.generate_expression(end)?;
32697        }
32698        if let Some(step) = &e.step {
32699            self.write(":");
32700            self.generate_expression(step)?;
32701        }
32702        Ok(())
32703    }
32704
32705    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32706        // SORT_ARRAY(this, asc)
32707        self.write_keyword("SORT_ARRAY");
32708        self.write("(");
32709        self.generate_expression(&e.this)?;
32710        if let Some(asc) = &e.asc {
32711            self.write(", ");
32712            self.generate_expression(asc)?;
32713        }
32714        self.write(")");
32715        Ok(())
32716    }
32717
32718    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32719        // SORT BY expressions
32720        self.write_keyword("SORT BY");
32721        self.write_space();
32722        for (i, expr) in e.expressions.iter().enumerate() {
32723            if i > 0 {
32724                self.write(", ");
32725            }
32726            self.generate_ordered(expr)?;
32727        }
32728        Ok(())
32729    }
32730
32731    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32732        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32733        if e.compound.is_some() {
32734            self.write_keyword("COMPOUND");
32735            self.write_space();
32736        }
32737        self.write_keyword("SORTKEY");
32738        self.write("(");
32739        // If this is a Tuple, unwrap its contents to avoid double parentheses
32740        if let Expression::Tuple(t) = e.this.as_ref() {
32741            for (i, expr) in t.expressions.iter().enumerate() {
32742                if i > 0 {
32743                    self.write(", ");
32744                }
32745                self.generate_expression(expr)?;
32746            }
32747        } else {
32748            self.generate_expression(&e.this)?;
32749        }
32750        self.write(")");
32751        Ok(())
32752    }
32753
32754    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32755        // SPLIT_PART(this, delimiter, part_index)
32756        self.write_keyword("SPLIT_PART");
32757        self.write("(");
32758        self.generate_expression(&e.this)?;
32759        if let Some(delimiter) = &e.delimiter {
32760            self.write(", ");
32761            self.generate_expression(delimiter)?;
32762        }
32763        if let Some(part_index) = &e.part_index {
32764            self.write(", ");
32765            self.generate_expression(part_index)?;
32766        }
32767        self.write(")");
32768        Ok(())
32769    }
32770
32771    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32772        // READS SQL DATA or MODIFIES SQL DATA, etc.
32773        self.generate_expression(&e.this)?;
32774        Ok(())
32775    }
32776
32777    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32778        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32779        self.write_keyword("SQL SECURITY");
32780        self.write_space();
32781        self.generate_expression(&e.this)?;
32782        Ok(())
32783    }
32784
32785    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32786        // ST_DISTANCE(this, expression, [use_spheroid])
32787        self.write_keyword("ST_DISTANCE");
32788        self.write("(");
32789        self.generate_expression(&e.this)?;
32790        self.write(", ");
32791        self.generate_expression(&e.expression)?;
32792        if let Some(use_spheroid) = &e.use_spheroid {
32793            self.write(", ");
32794            self.generate_expression(use_spheroid)?;
32795        }
32796        self.write(")");
32797        Ok(())
32798    }
32799
32800    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32801        // ST_POINT(this, expression)
32802        self.write_keyword("ST_POINT");
32803        self.write("(");
32804        self.generate_expression(&e.this)?;
32805        self.write(", ");
32806        self.generate_expression(&e.expression)?;
32807        self.write(")");
32808        Ok(())
32809    }
32810
32811    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32812        // IMMUTABLE, STABLE, VOLATILE
32813        self.generate_expression(&e.this)?;
32814        Ok(())
32815    }
32816
32817    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32818        // STANDARD_HASH(this, [expression])
32819        self.write_keyword("STANDARD_HASH");
32820        self.write("(");
32821        self.generate_expression(&e.this)?;
32822        if let Some(expression) = &e.expression {
32823            self.write(", ");
32824            self.generate_expression(expression)?;
32825        }
32826        self.write(")");
32827        Ok(())
32828    }
32829
32830    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32831        // STORED BY this
32832        self.write_keyword("STORED BY");
32833        self.write_space();
32834        self.generate_expression(&e.this)?;
32835        Ok(())
32836    }
32837
32838    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32839        // STRPOS(this, substr) or STRPOS(this, substr, position)
32840        // Different dialects have different function names
32841        use crate::dialects::DialectType;
32842        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32843            // Snowflake: CHARINDEX(substr, str[, position])
32844            self.write_keyword("CHARINDEX");
32845            self.write("(");
32846            if let Some(substr) = &e.substr {
32847                self.generate_expression(substr)?;
32848                self.write(", ");
32849            }
32850            self.generate_expression(&e.this)?;
32851            if let Some(position) = &e.position {
32852                self.write(", ");
32853                self.generate_expression(position)?;
32854            }
32855            self.write(")");
32856        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32857            self.write_keyword("POSITION");
32858            self.write("(");
32859            self.generate_expression(&e.this)?;
32860            if let Some(substr) = &e.substr {
32861                self.write(", ");
32862                self.generate_expression(substr)?;
32863            }
32864            if let Some(position) = &e.position {
32865                self.write(", ");
32866                self.generate_expression(position)?;
32867            }
32868            if let Some(occurrence) = &e.occurrence {
32869                self.write(", ");
32870                self.generate_expression(occurrence)?;
32871            }
32872            self.write(")");
32873        } else if matches!(
32874            self.config.dialect,
32875            Some(DialectType::SQLite)
32876                | Some(DialectType::Oracle)
32877                | Some(DialectType::BigQuery)
32878                | Some(DialectType::Teradata)
32879        ) {
32880            self.write_keyword("INSTR");
32881            self.write("(");
32882            self.generate_expression(&e.this)?;
32883            if let Some(substr) = &e.substr {
32884                self.write(", ");
32885                self.generate_expression(substr)?;
32886            }
32887            if let Some(position) = &e.position {
32888                self.write(", ");
32889                self.generate_expression(position)?;
32890            } else if e.occurrence.is_some() {
32891                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32892                // Default start position is 1
32893                self.write(", 1");
32894            }
32895            if let Some(occurrence) = &e.occurrence {
32896                self.write(", ");
32897                self.generate_expression(occurrence)?;
32898            }
32899            self.write(")");
32900        } else if matches!(
32901            self.config.dialect,
32902            Some(DialectType::MySQL)
32903                | Some(DialectType::SingleStore)
32904                | Some(DialectType::Doris)
32905                | Some(DialectType::StarRocks)
32906                | Some(DialectType::Hive)
32907                | Some(DialectType::Spark)
32908                | Some(DialectType::Databricks)
32909        ) {
32910            // LOCATE(substr, str[, position]) - substr first
32911            self.write_keyword("LOCATE");
32912            self.write("(");
32913            if let Some(substr) = &e.substr {
32914                self.generate_expression(substr)?;
32915                self.write(", ");
32916            }
32917            self.generate_expression(&e.this)?;
32918            if let Some(position) = &e.position {
32919                self.write(", ");
32920                self.generate_expression(position)?;
32921            }
32922            self.write(")");
32923        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32924            // CHARINDEX(substr, str[, position])
32925            self.write_keyword("CHARINDEX");
32926            self.write("(");
32927            if let Some(substr) = &e.substr {
32928                self.generate_expression(substr)?;
32929                self.write(", ");
32930            }
32931            self.generate_expression(&e.this)?;
32932            if let Some(position) = &e.position {
32933                self.write(", ");
32934                self.generate_expression(position)?;
32935            }
32936            self.write(")");
32937        } else if matches!(
32938            self.config.dialect,
32939            Some(DialectType::PostgreSQL)
32940                | Some(DialectType::Materialize)
32941                | Some(DialectType::RisingWave)
32942                | Some(DialectType::Redshift)
32943        ) {
32944            // POSITION(substr IN str) syntax
32945            self.write_keyword("POSITION");
32946            self.write("(");
32947            if let Some(substr) = &e.substr {
32948                self.generate_expression(substr)?;
32949                self.write(" IN ");
32950            }
32951            self.generate_expression(&e.this)?;
32952            self.write(")");
32953        } else {
32954            self.write_keyword("STRPOS");
32955            self.write("(");
32956            self.generate_expression(&e.this)?;
32957            if let Some(substr) = &e.substr {
32958                self.write(", ");
32959                self.generate_expression(substr)?;
32960            }
32961            if let Some(position) = &e.position {
32962                self.write(", ");
32963                self.generate_expression(position)?;
32964            }
32965            if let Some(occurrence) = &e.occurrence {
32966                self.write(", ");
32967                self.generate_expression(occurrence)?;
32968            }
32969            self.write(")");
32970        }
32971        Ok(())
32972    }
32973
32974    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32975        match self.config.dialect {
32976            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32977                // TO_DATE(this, java_format)
32978                self.write_keyword("TO_DATE");
32979                self.write("(");
32980                self.generate_expression(&e.this)?;
32981                if let Some(format) = &e.format {
32982                    self.write(", '");
32983                    self.write(&Self::strftime_to_java_format(format));
32984                    self.write("'");
32985                }
32986                self.write(")");
32987            }
32988            Some(DialectType::DuckDB) => {
32989                // CAST(STRPTIME(this, format) AS DATE)
32990                self.write_keyword("CAST");
32991                self.write("(");
32992                self.write_keyword("STRPTIME");
32993                self.write("(");
32994                self.generate_expression(&e.this)?;
32995                if let Some(format) = &e.format {
32996                    self.write(", '");
32997                    self.write(format);
32998                    self.write("'");
32999                }
33000                self.write(")");
33001                self.write_keyword(" AS ");
33002                self.write_keyword("DATE");
33003                self.write(")");
33004            }
33005            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
33006                // TO_DATE(this, pg_format)
33007                self.write_keyword("TO_DATE");
33008                self.write("(");
33009                self.generate_expression(&e.this)?;
33010                if let Some(format) = &e.format {
33011                    self.write(", '");
33012                    self.write(&Self::strftime_to_postgres_format(format));
33013                    self.write("'");
33014                }
33015                self.write(")");
33016            }
33017            Some(DialectType::BigQuery) => {
33018                // PARSE_DATE(format, this) - note: format comes first for BigQuery
33019                self.write_keyword("PARSE_DATE");
33020                self.write("(");
33021                if let Some(format) = &e.format {
33022                    self.write("'");
33023                    self.write(format);
33024                    self.write("'");
33025                    self.write(", ");
33026                }
33027                self.generate_expression(&e.this)?;
33028                self.write(")");
33029            }
33030            Some(DialectType::Teradata) => {
33031                // CAST(this AS DATE FORMAT 'teradata_fmt')
33032                self.write_keyword("CAST");
33033                self.write("(");
33034                self.generate_expression(&e.this)?;
33035                self.write_keyword(" AS ");
33036                self.write_keyword("DATE");
33037                if let Some(format) = &e.format {
33038                    self.write_keyword(" FORMAT ");
33039                    self.write("'");
33040                    self.write(&Self::strftime_to_teradata_format(format));
33041                    self.write("'");
33042                }
33043                self.write(")");
33044            }
33045            _ => {
33046                // STR_TO_DATE(this, format) - MySQL default
33047                self.write_keyword("STR_TO_DATE");
33048                self.write("(");
33049                self.generate_expression(&e.this)?;
33050                if let Some(format) = &e.format {
33051                    self.write(", '");
33052                    self.write(format);
33053                    self.write("'");
33054                }
33055                self.write(")");
33056            }
33057        }
33058        Ok(())
33059    }
33060
33061    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
33062    fn strftime_to_teradata_format(fmt: &str) -> String {
33063        let mut result = fmt.to_string();
33064        result = result.replace("%Y", "YYYY");
33065        result = result.replace("%y", "YY");
33066        result = result.replace("%m", "MM");
33067        result = result.replace("%B", "MMMM");
33068        result = result.replace("%b", "MMM");
33069        result = result.replace("%d", "DD");
33070        result = result.replace("%j", "DDD");
33071        result = result.replace("%H", "HH");
33072        result = result.replace("%M", "MI");
33073        result = result.replace("%S", "SS");
33074        result = result.replace("%f", "SSSSSS");
33075        result = result.replace("%A", "EEEE");
33076        result = result.replace("%a", "EEE");
33077        result
33078    }
33079
33080    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33081    /// Public static version for use by other modules
33082    pub fn strftime_to_java_format_static(fmt: &str) -> String {
33083        Self::strftime_to_java_format(fmt)
33084    }
33085
33086    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33087    fn strftime_to_java_format(fmt: &str) -> String {
33088        let mut result = fmt.to_string();
33089        // Handle non-padded variants BEFORE their padded counterparts
33090        result = result.replace("%-d", "d");
33091        result = result.replace("%-m", "M");
33092        result = result.replace("%-H", "H");
33093        result = result.replace("%-M", "m");
33094        result = result.replace("%-S", "s");
33095        result = result.replace("%Y", "yyyy");
33096        result = result.replace("%y", "yy");
33097        result = result.replace("%m", "MM");
33098        result = result.replace("%B", "MMMM");
33099        result = result.replace("%b", "MMM");
33100        result = result.replace("%d", "dd");
33101        result = result.replace("%j", "DDD");
33102        result = result.replace("%H", "HH");
33103        result = result.replace("%M", "mm");
33104        result = result.replace("%S", "ss");
33105        result = result.replace("%f", "SSSSSS");
33106        result = result.replace("%A", "EEEE");
33107        result = result.replace("%a", "EEE");
33108        result
33109    }
33110
33111    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
33112    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
33113    fn strftime_to_tsql_format(fmt: &str) -> String {
33114        let mut result = fmt.to_string();
33115        // Handle non-padded variants BEFORE their padded counterparts
33116        result = result.replace("%-d", "d");
33117        result = result.replace("%-m", "M");
33118        result = result.replace("%-H", "H");
33119        result = result.replace("%-M", "m");
33120        result = result.replace("%-S", "s");
33121        result = result.replace("%Y", "yyyy");
33122        result = result.replace("%y", "yy");
33123        result = result.replace("%m", "MM");
33124        result = result.replace("%B", "MMMM");
33125        result = result.replace("%b", "MMM");
33126        result = result.replace("%d", "dd");
33127        result = result.replace("%j", "DDD");
33128        result = result.replace("%H", "HH");
33129        result = result.replace("%M", "mm");
33130        result = result.replace("%S", "ss");
33131        result = result.replace("%f", "ffffff");
33132        result = result.replace("%A", "dddd");
33133        result = result.replace("%a", "ddd");
33134        result
33135    }
33136
33137    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33138    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33139    fn decompose_json_path(path: &str) -> Vec<String> {
33140        let mut parts = Vec::new();
33141        // Strip leading $ and optional .
33142        let path = if path.starts_with("$.") {
33143            &path[2..]
33144        } else if path.starts_with('$') {
33145            &path[1..]
33146        } else {
33147            path
33148        };
33149        if path.is_empty() {
33150            return parts;
33151        }
33152        let mut current = String::new();
33153        let chars: Vec<char> = path.chars().collect();
33154        let mut i = 0;
33155        while i < chars.len() {
33156            match chars[i] {
33157                '.' => {
33158                    if !current.is_empty() {
33159                        parts.push(current.clone());
33160                        current.clear();
33161                    }
33162                    i += 1;
33163                }
33164                '[' => {
33165                    if !current.is_empty() {
33166                        parts.push(current.clone());
33167                        current.clear();
33168                    }
33169                    i += 1;
33170                    // Read the content inside brackets
33171                    let mut bracket_content = String::new();
33172                    while i < chars.len() && chars[i] != ']' {
33173                        // Skip quotes inside brackets
33174                        if chars[i] == '"' || chars[i] == '\'' {
33175                            let quote = chars[i];
33176                            i += 1;
33177                            while i < chars.len() && chars[i] != quote {
33178                                bracket_content.push(chars[i]);
33179                                i += 1;
33180                            }
33181                            if i < chars.len() {
33182                                i += 1;
33183                            } // skip closing quote
33184                        } else {
33185                            bracket_content.push(chars[i]);
33186                            i += 1;
33187                        }
33188                    }
33189                    if i < chars.len() {
33190                        i += 1;
33191                    } // skip ]
33192                      // Skip wildcard [*] - don't add as a part
33193                    if bracket_content != "*" {
33194                        parts.push(bracket_content);
33195                    }
33196                }
33197                _ => {
33198                    current.push(chars[i]);
33199                    i += 1;
33200                }
33201            }
33202        }
33203        if !current.is_empty() {
33204            parts.push(current);
33205        }
33206        parts
33207    }
33208
33209    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
33210    fn strftime_to_postgres_format(fmt: &str) -> String {
33211        let mut result = fmt.to_string();
33212        // Handle non-padded variants BEFORE their padded counterparts
33213        result = result.replace("%-d", "FMDD");
33214        result = result.replace("%-m", "FMMM");
33215        result = result.replace("%-H", "FMHH24");
33216        result = result.replace("%-M", "FMMI");
33217        result = result.replace("%-S", "FMSS");
33218        result = result.replace("%Y", "YYYY");
33219        result = result.replace("%y", "YY");
33220        result = result.replace("%m", "MM");
33221        result = result.replace("%B", "Month");
33222        result = result.replace("%b", "Mon");
33223        result = result.replace("%d", "DD");
33224        result = result.replace("%j", "DDD");
33225        result = result.replace("%H", "HH24");
33226        result = result.replace("%M", "MI");
33227        result = result.replace("%S", "SS");
33228        result = result.replace("%f", "US");
33229        result = result.replace("%A", "Day");
33230        result = result.replace("%a", "Dy");
33231        result
33232    }
33233
33234    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33235    fn strftime_to_snowflake_format(fmt: &str) -> String {
33236        let mut result = fmt.to_string();
33237        // Handle %-d (non-padded day) before %d (padded day)
33238        result = result.replace("%-d", "dd");
33239        result = result.replace("%-m", "mm"); // non-padded month
33240        result = result.replace("%Y", "yyyy");
33241        result = result.replace("%y", "yy");
33242        result = result.replace("%m", "mm");
33243        result = result.replace("%d", "DD");
33244        result = result.replace("%H", "hh24");
33245        result = result.replace("%M", "mi");
33246        result = result.replace("%S", "ss");
33247        result = result.replace("%f", "ff");
33248        result
33249    }
33250
33251    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33252        // STR_TO_MAP(this, pair_delim, key_value_delim)
33253        self.write_keyword("STR_TO_MAP");
33254        self.write("(");
33255        self.generate_expression(&e.this)?;
33256        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33257        let needs_defaults = matches!(
33258            self.config.dialect,
33259            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33260        );
33261        if let Some(pair_delim) = &e.pair_delim {
33262            self.write(", ");
33263            self.generate_expression(pair_delim)?;
33264        } else if needs_defaults {
33265            self.write(", ','");
33266        }
33267        if let Some(key_value_delim) = &e.key_value_delim {
33268            self.write(", ");
33269            self.generate_expression(key_value_delim)?;
33270        } else if needs_defaults {
33271            self.write(", ':'");
33272        }
33273        self.write(")");
33274        Ok(())
33275    }
33276
33277    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33278        // Detect format style: strftime (starts with %) vs Snowflake/Java
33279        let is_strftime = e.format.contains('%');
33280        // Helper: get strftime format from whatever style is stored
33281        let to_strftime = |f: &str| -> String {
33282            if is_strftime {
33283                f.to_string()
33284            } else {
33285                Self::snowflake_format_to_strftime(f)
33286            }
33287        };
33288        // Helper: get Java format
33289        let to_java = |f: &str| -> String {
33290            if is_strftime {
33291                Self::strftime_to_java_format(f)
33292            } else {
33293                Self::snowflake_format_to_spark(f)
33294            }
33295        };
33296        // Helper: get PG format
33297        let to_pg = |f: &str| -> String {
33298            if is_strftime {
33299                Self::strftime_to_postgres_format(f)
33300            } else {
33301                Self::convert_strptime_to_postgres_format(f)
33302            }
33303        };
33304
33305        match self.config.dialect {
33306            Some(DialectType::Exasol) => {
33307                self.write_keyword("TO_DATE");
33308                self.write("(");
33309                self.generate_expression(&e.this)?;
33310                self.write(", '");
33311                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33312                self.write("'");
33313                self.write(")");
33314            }
33315            Some(DialectType::BigQuery) => {
33316                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33317                let fmt = to_strftime(&e.format);
33318                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33319                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33320                self.write_keyword("PARSE_TIMESTAMP");
33321                self.write("('");
33322                self.write(&fmt);
33323                self.write("', ");
33324                self.generate_expression(&e.this)?;
33325                self.write(")");
33326            }
33327            Some(DialectType::Hive) => {
33328                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33329                // Check both the raw format and the converted format (in case it's already Java)
33330                let java_fmt = to_java(&e.format);
33331                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33332                    || java_fmt == "yyyy-MM-dd"
33333                    || e.format == "yyyy-MM-dd HH:mm:ss"
33334                    || e.format == "yyyy-MM-dd"
33335                {
33336                    self.write_keyword("CAST");
33337                    self.write("(");
33338                    self.generate_expression(&e.this)?;
33339                    self.write(" ");
33340                    self.write_keyword("AS TIMESTAMP");
33341                    self.write(")");
33342                } else {
33343                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33344                    self.write_keyword("CAST");
33345                    self.write("(");
33346                    self.write_keyword("FROM_UNIXTIME");
33347                    self.write("(");
33348                    self.write_keyword("UNIX_TIMESTAMP");
33349                    self.write("(");
33350                    self.generate_expression(&e.this)?;
33351                    self.write(", '");
33352                    self.write(&java_fmt);
33353                    self.write("')");
33354                    self.write(") ");
33355                    self.write_keyword("AS TIMESTAMP");
33356                    self.write(")");
33357                }
33358            }
33359            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33360                // Spark: TO_TIMESTAMP(value, java_format)
33361                let java_fmt = to_java(&e.format);
33362                self.write_keyword("TO_TIMESTAMP");
33363                self.write("(");
33364                self.generate_expression(&e.this)?;
33365                self.write(", '");
33366                self.write(&java_fmt);
33367                self.write("')");
33368            }
33369            Some(DialectType::MySQL) => {
33370                // MySQL: STR_TO_DATE(value, format)
33371                let mut fmt = to_strftime(&e.format);
33372                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33373                fmt = fmt.replace("%-d", "%e");
33374                fmt = fmt.replace("%-m", "%c");
33375                fmt = fmt.replace("%H:%M:%S", "%T");
33376                self.write_keyword("STR_TO_DATE");
33377                self.write("(");
33378                self.generate_expression(&e.this)?;
33379                self.write(", '");
33380                self.write(&fmt);
33381                self.write("')");
33382            }
33383            Some(DialectType::Drill) => {
33384                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33385                let java_fmt = to_java(&e.format);
33386                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33387                let java_fmt = java_fmt.replace('T', "''T''");
33388                self.write_keyword("TO_TIMESTAMP");
33389                self.write("(");
33390                self.generate_expression(&e.this)?;
33391                self.write(", '");
33392                self.write(&java_fmt);
33393                self.write("')");
33394            }
33395            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33396                // Presto: DATE_PARSE(value, strftime_format)
33397                let mut fmt = to_strftime(&e.format);
33398                // Presto uses %e for non-padded day, %T for %H:%M:%S
33399                fmt = fmt.replace("%-d", "%e");
33400                fmt = fmt.replace("%-m", "%c");
33401                fmt = fmt.replace("%H:%M:%S", "%T");
33402                self.write_keyword("DATE_PARSE");
33403                self.write("(");
33404                self.generate_expression(&e.this)?;
33405                self.write(", '");
33406                self.write(&fmt);
33407                self.write("')");
33408            }
33409            Some(DialectType::DuckDB) => {
33410                // DuckDB: STRPTIME(value, strftime_format)
33411                let fmt = to_strftime(&e.format);
33412                self.write_keyword("STRPTIME");
33413                self.write("(");
33414                self.generate_expression(&e.this)?;
33415                self.write(", '");
33416                self.write(&fmt);
33417                self.write("')");
33418            }
33419            Some(DialectType::PostgreSQL)
33420            | Some(DialectType::Redshift)
33421            | Some(DialectType::Materialize) => {
33422                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33423                let pg_fmt = to_pg(&e.format);
33424                self.write_keyword("TO_TIMESTAMP");
33425                self.write("(");
33426                self.generate_expression(&e.this)?;
33427                self.write(", '");
33428                self.write(&pg_fmt);
33429                self.write("')");
33430            }
33431            Some(DialectType::Oracle) => {
33432                // Oracle: TO_TIMESTAMP(value, pg_format)
33433                let pg_fmt = to_pg(&e.format);
33434                self.write_keyword("TO_TIMESTAMP");
33435                self.write("(");
33436                self.generate_expression(&e.this)?;
33437                self.write(", '");
33438                self.write(&pg_fmt);
33439                self.write("')");
33440            }
33441            Some(DialectType::Snowflake) => {
33442                // Snowflake: TO_TIMESTAMP(value, format) - native format
33443                self.write_keyword("TO_TIMESTAMP");
33444                self.write("(");
33445                self.generate_expression(&e.this)?;
33446                self.write(", '");
33447                self.write(&e.format);
33448                self.write("')");
33449            }
33450            _ => {
33451                // Default: STR_TO_TIME(this, format)
33452                self.write_keyword("STR_TO_TIME");
33453                self.write("(");
33454                self.generate_expression(&e.this)?;
33455                self.write(", '");
33456                self.write(&e.format);
33457                self.write("'");
33458                self.write(")");
33459            }
33460        }
33461        Ok(())
33462    }
33463
33464    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33465    fn snowflake_format_to_strftime(format: &str) -> String {
33466        let mut result = String::new();
33467        let chars: Vec<char> = format.chars().collect();
33468        let mut i = 0;
33469        while i < chars.len() {
33470            let remaining = &format[i..];
33471            if remaining.starts_with("yyyy") {
33472                result.push_str("%Y");
33473                i += 4;
33474            } else if remaining.starts_with("yy") {
33475                result.push_str("%y");
33476                i += 2;
33477            } else if remaining.starts_with("mmmm") {
33478                result.push_str("%B"); // full month name
33479                i += 4;
33480            } else if remaining.starts_with("mon") {
33481                result.push_str("%b"); // abbreviated month
33482                i += 3;
33483            } else if remaining.starts_with("mm") {
33484                result.push_str("%m");
33485                i += 2;
33486            } else if remaining.starts_with("DD") {
33487                result.push_str("%d");
33488                i += 2;
33489            } else if remaining.starts_with("dy") {
33490                result.push_str("%a"); // abbreviated day name
33491                i += 2;
33492            } else if remaining.starts_with("hh24") {
33493                result.push_str("%H");
33494                i += 4;
33495            } else if remaining.starts_with("hh12") {
33496                result.push_str("%I");
33497                i += 4;
33498            } else if remaining.starts_with("hh") {
33499                result.push_str("%H");
33500                i += 2;
33501            } else if remaining.starts_with("mi") {
33502                result.push_str("%M");
33503                i += 2;
33504            } else if remaining.starts_with("ss") {
33505                result.push_str("%S");
33506                i += 2;
33507            } else if remaining.starts_with("ff") {
33508                // Fractional seconds
33509                result.push_str("%f");
33510                i += 2;
33511                // Skip digits after ff (ff3, ff6, ff9)
33512                while i < chars.len() && chars[i].is_ascii_digit() {
33513                    i += 1;
33514                }
33515            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33516                result.push_str("%p");
33517                i += 2;
33518            } else if remaining.starts_with("tz") {
33519                result.push_str("%Z");
33520                i += 2;
33521            } else {
33522                result.push(chars[i]);
33523                i += 1;
33524            }
33525        }
33526        result
33527    }
33528
33529    /// Convert Snowflake normalized format to Spark format (Java-style)
33530    fn snowflake_format_to_spark(format: &str) -> String {
33531        let mut result = String::new();
33532        let chars: Vec<char> = format.chars().collect();
33533        let mut i = 0;
33534        while i < chars.len() {
33535            let remaining = &format[i..];
33536            if remaining.starts_with("yyyy") {
33537                result.push_str("yyyy");
33538                i += 4;
33539            } else if remaining.starts_with("yy") {
33540                result.push_str("yy");
33541                i += 2;
33542            } else if remaining.starts_with("mmmm") {
33543                result.push_str("MMMM"); // full month name
33544                i += 4;
33545            } else if remaining.starts_with("mon") {
33546                result.push_str("MMM"); // abbreviated month
33547                i += 3;
33548            } else if remaining.starts_with("mm") {
33549                result.push_str("MM");
33550                i += 2;
33551            } else if remaining.starts_with("DD") {
33552                result.push_str("dd");
33553                i += 2;
33554            } else if remaining.starts_with("dy") {
33555                result.push_str("EEE"); // abbreviated day name
33556                i += 2;
33557            } else if remaining.starts_with("hh24") {
33558                result.push_str("HH");
33559                i += 4;
33560            } else if remaining.starts_with("hh12") {
33561                result.push_str("hh");
33562                i += 4;
33563            } else if remaining.starts_with("hh") {
33564                result.push_str("HH");
33565                i += 2;
33566            } else if remaining.starts_with("mi") {
33567                result.push_str("mm");
33568                i += 2;
33569            } else if remaining.starts_with("ss") {
33570                result.push_str("ss");
33571                i += 2;
33572            } else if remaining.starts_with("ff") {
33573                result.push_str("SSS"); // milliseconds
33574                i += 2;
33575                // Skip digits after ff
33576                while i < chars.len() && chars[i].is_ascii_digit() {
33577                    i += 1;
33578                }
33579            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33580                result.push_str("a");
33581                i += 2;
33582            } else if remaining.starts_with("tz") {
33583                result.push_str("z");
33584                i += 2;
33585            } else {
33586                result.push(chars[i]);
33587                i += 1;
33588            }
33589        }
33590        result
33591    }
33592
33593    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33594        match self.config.dialect {
33595            Some(DialectType::DuckDB) => {
33596                // DuckDB: EPOCH(STRPTIME(value, format))
33597                self.write_keyword("EPOCH");
33598                self.write("(");
33599                self.write_keyword("STRPTIME");
33600                self.write("(");
33601                if let Some(this) = &e.this {
33602                    self.generate_expression(this)?;
33603                }
33604                if let Some(format) = &e.format {
33605                    self.write(", '");
33606                    self.write(format);
33607                    self.write("'");
33608                }
33609                self.write("))");
33610            }
33611            Some(DialectType::Hive) => {
33612                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33613                self.write_keyword("UNIX_TIMESTAMP");
33614                self.write("(");
33615                if let Some(this) = &e.this {
33616                    self.generate_expression(this)?;
33617                }
33618                if let Some(format) = &e.format {
33619                    let java_fmt = Self::strftime_to_java_format(format);
33620                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33621                        self.write(", '");
33622                        self.write(&java_fmt);
33623                        self.write("'");
33624                    }
33625                }
33626                self.write(")");
33627            }
33628            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33629                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33630                self.write_keyword("UNIX_TIMESTAMP");
33631                self.write("(");
33632                if let Some(this) = &e.this {
33633                    self.generate_expression(this)?;
33634                }
33635                if let Some(format) = &e.format {
33636                    self.write(", '");
33637                    self.write(format);
33638                    self.write("'");
33639                }
33640                self.write(")");
33641            }
33642            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33643                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33644                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33645                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33646                let java_fmt = Self::strftime_to_java_format(c_fmt);
33647                self.write_keyword("TO_UNIXTIME");
33648                self.write("(");
33649                self.write_keyword("COALESCE");
33650                self.write("(");
33651                self.write_keyword("TRY");
33652                self.write("(");
33653                self.write_keyword("DATE_PARSE");
33654                self.write("(");
33655                self.write_keyword("CAST");
33656                self.write("(");
33657                if let Some(this) = &e.this {
33658                    self.generate_expression(this)?;
33659                }
33660                self.write(" ");
33661                self.write_keyword("AS VARCHAR");
33662                self.write("), '");
33663                self.write(c_fmt);
33664                self.write("')), ");
33665                self.write_keyword("PARSE_DATETIME");
33666                self.write("(");
33667                self.write_keyword("DATE_FORMAT");
33668                self.write("(");
33669                self.write_keyword("CAST");
33670                self.write("(");
33671                if let Some(this) = &e.this {
33672                    self.generate_expression(this)?;
33673                }
33674                self.write(" ");
33675                self.write_keyword("AS TIMESTAMP");
33676                self.write("), '");
33677                self.write(c_fmt);
33678                self.write("'), '");
33679                self.write(&java_fmt);
33680                self.write("')))");
33681            }
33682            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33683                // Spark: UNIX_TIMESTAMP(value, java_format)
33684                self.write_keyword("UNIX_TIMESTAMP");
33685                self.write("(");
33686                if let Some(this) = &e.this {
33687                    self.generate_expression(this)?;
33688                }
33689                if let Some(format) = &e.format {
33690                    let java_fmt = Self::strftime_to_java_format(format);
33691                    self.write(", '");
33692                    self.write(&java_fmt);
33693                    self.write("'");
33694                }
33695                self.write(")");
33696            }
33697            _ => {
33698                // Default: STR_TO_UNIX(this, format)
33699                self.write_keyword("STR_TO_UNIX");
33700                self.write("(");
33701                if let Some(this) = &e.this {
33702                    self.generate_expression(this)?;
33703                }
33704                if let Some(format) = &e.format {
33705                    self.write(", '");
33706                    self.write(format);
33707                    self.write("'");
33708                }
33709                self.write(")");
33710            }
33711        }
33712        Ok(())
33713    }
33714
33715    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33716        // STRING_TO_ARRAY(this, delimiter, null_string)
33717        self.write_keyword("STRING_TO_ARRAY");
33718        self.write("(");
33719        self.generate_expression(&e.this)?;
33720        if let Some(expression) = &e.expression {
33721            self.write(", ");
33722            self.generate_expression(expression)?;
33723        }
33724        if let Some(null_val) = &e.null {
33725            self.write(", ");
33726            self.generate_expression(null_val)?;
33727        }
33728        self.write(")");
33729        Ok(())
33730    }
33731
33732    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33733        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33734            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33735            self.write_keyword("OBJECT_CONSTRUCT");
33736            self.write("(");
33737            for (i, (name, expr)) in e.fields.iter().enumerate() {
33738                if i > 0 {
33739                    self.write(", ");
33740                }
33741                if let Some(name) = name {
33742                    self.write("'");
33743                    self.write(name);
33744                    self.write("'");
33745                    self.write(", ");
33746                } else {
33747                    self.write("'_");
33748                    self.write(&i.to_string());
33749                    self.write("'");
33750                    self.write(", ");
33751                }
33752                self.generate_expression(expr)?;
33753            }
33754            self.write(")");
33755        } else if self.config.struct_curly_brace_notation {
33756            // DuckDB-style: {'key': value, ...}
33757            self.write("{");
33758            for (i, (name, expr)) in e.fields.iter().enumerate() {
33759                if i > 0 {
33760                    self.write(", ");
33761                }
33762                if let Some(name) = name {
33763                    // Quote the key as a string literal
33764                    self.write("'");
33765                    self.write(name);
33766                    self.write("'");
33767                    self.write(": ");
33768                } else {
33769                    // Unnamed field: use positional key
33770                    self.write("'_");
33771                    self.write(&i.to_string());
33772                    self.write("'");
33773                    self.write(": ");
33774                }
33775                self.generate_expression(expr)?;
33776            }
33777            self.write("}");
33778        } else {
33779            // Standard SQL struct notation
33780            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33781            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33782            let value_as_name = matches!(
33783                self.config.dialect,
33784                Some(DialectType::BigQuery)
33785                    | Some(DialectType::Spark)
33786                    | Some(DialectType::Databricks)
33787                    | Some(DialectType::Hive)
33788            );
33789            self.write_keyword("STRUCT");
33790            self.write("(");
33791            for (i, (name, expr)) in e.fields.iter().enumerate() {
33792                if i > 0 {
33793                    self.write(", ");
33794                }
33795                if let Some(name) = name {
33796                    if value_as_name {
33797                        // STRUCT(value AS name)
33798                        self.generate_expression(expr)?;
33799                        self.write_space();
33800                        self.write_keyword("AS");
33801                        self.write_space();
33802                        // Quote name if it contains spaces or special chars
33803                        let needs_quoting = name.contains(' ') || name.contains('-');
33804                        if needs_quoting {
33805                            if matches!(
33806                                self.config.dialect,
33807                                Some(DialectType::Spark)
33808                                    | Some(DialectType::Databricks)
33809                                    | Some(DialectType::Hive)
33810                            ) {
33811                                self.write("`");
33812                                self.write(name);
33813                                self.write("`");
33814                            } else {
33815                                self.write(name);
33816                            }
33817                        } else {
33818                            self.write(name);
33819                        }
33820                    } else {
33821                        // STRUCT(name AS value)
33822                        self.write(name);
33823                        self.write_space();
33824                        self.write_keyword("AS");
33825                        self.write_space();
33826                        self.generate_expression(expr)?;
33827                    }
33828                } else {
33829                    self.generate_expression(expr)?;
33830                }
33831            }
33832            self.write(")");
33833        }
33834        Ok(())
33835    }
33836
33837    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33838        // STUFF(this, start, length, expression)
33839        self.write_keyword("STUFF");
33840        self.write("(");
33841        self.generate_expression(&e.this)?;
33842        if let Some(start) = &e.start {
33843            self.write(", ");
33844            self.generate_expression(start)?;
33845        }
33846        if let Some(length) = e.length {
33847            self.write(", ");
33848            self.write(&length.to_string());
33849        }
33850        self.write(", ");
33851        self.generate_expression(&e.expression)?;
33852        self.write(")");
33853        Ok(())
33854    }
33855
33856    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33857        // SUBSTRING_INDEX(this, delimiter, count)
33858        self.write_keyword("SUBSTRING_INDEX");
33859        self.write("(");
33860        self.generate_expression(&e.this)?;
33861        if let Some(delimiter) = &e.delimiter {
33862            self.write(", ");
33863            self.generate_expression(delimiter)?;
33864        }
33865        if let Some(count) = &e.count {
33866            self.write(", ");
33867            self.generate_expression(count)?;
33868        }
33869        self.write(")");
33870        Ok(())
33871    }
33872
33873    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33874        // SUMMARIZE [TABLE] this
33875        self.write_keyword("SUMMARIZE");
33876        if e.table.is_some() {
33877            self.write_space();
33878            self.write_keyword("TABLE");
33879        }
33880        self.write_space();
33881        self.generate_expression(&e.this)?;
33882        Ok(())
33883    }
33884
33885    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33886        // SYSTIMESTAMP
33887        self.write_keyword("SYSTIMESTAMP");
33888        Ok(())
33889    }
33890
33891    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33892        // alias (columns...)
33893        if let Some(this) = &e.this {
33894            self.generate_expression(this)?;
33895        }
33896        if !e.columns.is_empty() {
33897            self.write("(");
33898            for (i, col) in e.columns.iter().enumerate() {
33899                if i > 0 {
33900                    self.write(", ");
33901                }
33902                self.generate_expression(col)?;
33903            }
33904            self.write(")");
33905        }
33906        Ok(())
33907    }
33908
33909    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33910        // TABLE(this) [AS alias]
33911        self.write_keyword("TABLE");
33912        self.write("(");
33913        self.generate_expression(&e.this)?;
33914        self.write(")");
33915        if let Some(alias) = &e.alias {
33916            self.write_space();
33917            self.write_keyword("AS");
33918            self.write_space();
33919            self.write(alias);
33920        }
33921        Ok(())
33922    }
33923
33924    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33925        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33926        self.write_keyword("ROWS FROM");
33927        self.write(" (");
33928        for (i, expr) in e.expressions.iter().enumerate() {
33929            if i > 0 {
33930                self.write(", ");
33931            }
33932            // Each expression is either:
33933            // - A plain function (no alias)
33934            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33935            match expr {
33936                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33937                    // First element is the function, second is the TableAlias
33938                    self.generate_expression(&tuple.expressions[0])?;
33939                    self.write_space();
33940                    self.write_keyword("AS");
33941                    self.write_space();
33942                    self.generate_expression(&tuple.expressions[1])?;
33943                }
33944                _ => {
33945                    self.generate_expression(expr)?;
33946                }
33947            }
33948        }
33949        self.write(")");
33950        if e.ordinality {
33951            self.write_space();
33952            self.write_keyword("WITH ORDINALITY");
33953        }
33954        if let Some(alias) = &e.alias {
33955            self.write_space();
33956            self.write_keyword("AS");
33957            self.write_space();
33958            self.generate_expression(alias)?;
33959        }
33960        Ok(())
33961    }
33962
33963    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33964        use crate::dialects::DialectType;
33965
33966        // New wrapper pattern: expression + Sample struct
33967        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33968            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33969            if self.config.alias_post_tablesample {
33970                // Handle Subquery with alias and Alias wrapper
33971                if let Expression::Subquery(ref s) = **this {
33972                    if let Some(ref alias) = s.alias {
33973                        // Create a clone without alias for output
33974                        let mut subquery_no_alias = (**s).clone();
33975                        subquery_no_alias.alias = None;
33976                        subquery_no_alias.column_aliases = Vec::new();
33977                        self.generate_expression(&Expression::Subquery(Box::new(
33978                            subquery_no_alias,
33979                        )))?;
33980                        self.write_space();
33981                        self.write_keyword("TABLESAMPLE");
33982                        self.generate_sample_body(sample)?;
33983                        if let Some(ref seed) = sample.seed {
33984                            self.write_space();
33985                            let use_seed = sample.use_seed_keyword
33986                                && !matches!(
33987                                    self.config.dialect,
33988                                    Some(crate::dialects::DialectType::Databricks)
33989                                        | Some(crate::dialects::DialectType::Spark)
33990                                );
33991                            if use_seed {
33992                                self.write_keyword("SEED");
33993                            } else {
33994                                self.write_keyword("REPEATABLE");
33995                            }
33996                            self.write(" (");
33997                            self.generate_expression(seed)?;
33998                            self.write(")");
33999                        }
34000                        self.write_space();
34001                        self.write_keyword("AS");
34002                        self.write_space();
34003                        self.generate_identifier(alias)?;
34004                        return Ok(());
34005                    }
34006                } else if let Expression::Alias(ref a) = **this {
34007                    // Output the base expression without alias
34008                    self.generate_expression(&a.this)?;
34009                    self.write_space();
34010                    self.write_keyword("TABLESAMPLE");
34011                    self.generate_sample_body(sample)?;
34012                    if let Some(ref seed) = sample.seed {
34013                        self.write_space();
34014                        let use_seed = sample.use_seed_keyword
34015                            && !matches!(
34016                                self.config.dialect,
34017                                Some(crate::dialects::DialectType::Databricks)
34018                                    | Some(crate::dialects::DialectType::Spark)
34019                            );
34020                        if use_seed {
34021                            self.write_keyword("SEED");
34022                        } else {
34023                            self.write_keyword("REPEATABLE");
34024                        }
34025                        self.write(" (");
34026                        self.generate_expression(seed)?;
34027                        self.write(")");
34028                    }
34029                    // Output alias after TABLESAMPLE
34030                    self.write_space();
34031                    self.write_keyword("AS");
34032                    self.write_space();
34033                    self.generate_identifier(&a.alias)?;
34034                    return Ok(());
34035                }
34036            }
34037            // Default: generate wrapped expression first, then TABLESAMPLE
34038            self.generate_expression(this)?;
34039            self.write_space();
34040            self.write_keyword("TABLESAMPLE");
34041            self.generate_sample_body(sample)?;
34042            // Seed for table-level sample
34043            if let Some(ref seed) = sample.seed {
34044                self.write_space();
34045                // Databricks uses REPEATABLE, not SEED
34046                let use_seed = sample.use_seed_keyword
34047                    && !matches!(
34048                        self.config.dialect,
34049                        Some(crate::dialects::DialectType::Databricks)
34050                            | Some(crate::dialects::DialectType::Spark)
34051                    );
34052                if use_seed {
34053                    self.write_keyword("SEED");
34054                } else {
34055                    self.write_keyword("REPEATABLE");
34056                }
34057                self.write(" (");
34058                self.generate_expression(seed)?;
34059                self.write(")");
34060            }
34061            return Ok(());
34062        }
34063
34064        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
34065        self.write_keyword("TABLESAMPLE");
34066        if let Some(method) = &e.method {
34067            self.write_space();
34068            self.write_keyword(method);
34069        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34070            // Snowflake defaults to BERNOULLI when no method is specified
34071            self.write_space();
34072            self.write_keyword("BERNOULLI");
34073        }
34074        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
34075            self.write_space();
34076            self.write_keyword("BUCKET");
34077            self.write_space();
34078            self.generate_expression(numerator)?;
34079            self.write_space();
34080            self.write_keyword("OUT OF");
34081            self.write_space();
34082            self.generate_expression(denominator)?;
34083            if let Some(field) = &e.bucket_field {
34084                self.write_space();
34085                self.write_keyword("ON");
34086                self.write_space();
34087                self.generate_expression(field)?;
34088            }
34089        } else if !e.expressions.is_empty() {
34090            self.write(" (");
34091            for (i, expr) in e.expressions.iter().enumerate() {
34092                if i > 0 {
34093                    self.write(", ");
34094                }
34095                self.generate_expression(expr)?;
34096            }
34097            self.write(")");
34098        } else if let Some(percent) = &e.percent {
34099            self.write(" (");
34100            self.generate_expression(percent)?;
34101            self.write_space();
34102            self.write_keyword("PERCENT");
34103            self.write(")");
34104        }
34105        Ok(())
34106    }
34107
34108    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
34109        // [prefix]this[postfix]
34110        if let Some(prefix) = &e.prefix {
34111            self.generate_expression(prefix)?;
34112        }
34113        if let Some(this) = &e.this {
34114            self.generate_expression(this)?;
34115        }
34116        if let Some(postfix) = &e.postfix {
34117            self.generate_expression(postfix)?;
34118        }
34119        Ok(())
34120    }
34121
34122    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
34123        // TAG (expressions)
34124        self.write_keyword("TAG");
34125        self.write(" (");
34126        for (i, expr) in e.expressions.iter().enumerate() {
34127            if i > 0 {
34128                self.write(", ");
34129            }
34130            self.generate_expression(expr)?;
34131        }
34132        self.write(")");
34133        Ok(())
34134    }
34135
34136    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
34137        // TEMPORARY or TEMP or [this] TEMPORARY
34138        if let Some(this) = &e.this {
34139            self.generate_expression(this)?;
34140            self.write_space();
34141        }
34142        self.write_keyword("TEMPORARY");
34143        Ok(())
34144    }
34145
34146    /// Generate a Time function expression
34147    /// For most dialects: TIME('value')
34148    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
34149        // Standard: TIME(value)
34150        self.write_keyword("TIME");
34151        self.write("(");
34152        self.generate_expression(&e.this)?;
34153        self.write(")");
34154        Ok(())
34155    }
34156
34157    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
34158        // TIME_ADD(this, expression, unit)
34159        self.write_keyword("TIME_ADD");
34160        self.write("(");
34161        self.generate_expression(&e.this)?;
34162        self.write(", ");
34163        self.generate_expression(&e.expression)?;
34164        if let Some(unit) = &e.unit {
34165            self.write(", ");
34166            self.write_keyword(unit);
34167        }
34168        self.write(")");
34169        Ok(())
34170    }
34171
34172    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
34173        // TIME_DIFF(this, expression, unit)
34174        self.write_keyword("TIME_DIFF");
34175        self.write("(");
34176        self.generate_expression(&e.this)?;
34177        self.write(", ");
34178        self.generate_expression(&e.expression)?;
34179        if let Some(unit) = &e.unit {
34180            self.write(", ");
34181            self.write_keyword(unit);
34182        }
34183        self.write(")");
34184        Ok(())
34185    }
34186
34187    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
34188        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
34189        self.write_keyword("TIME_FROM_PARTS");
34190        self.write("(");
34191        let mut first = true;
34192        if let Some(hour) = &e.hour {
34193            self.generate_expression(hour)?;
34194            first = false;
34195        }
34196        if let Some(minute) = &e.min {
34197            if !first {
34198                self.write(", ");
34199            }
34200            self.generate_expression(minute)?;
34201            first = false;
34202        }
34203        if let Some(second) = &e.sec {
34204            if !first {
34205                self.write(", ");
34206            }
34207            self.generate_expression(second)?;
34208            first = false;
34209        }
34210        if let Some(ns) = &e.nano {
34211            if !first {
34212                self.write(", ");
34213            }
34214            self.generate_expression(ns)?;
34215        }
34216        self.write(")");
34217        Ok(())
34218    }
34219
34220    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34221        // TIME_SLICE(this, expression, unit)
34222        self.write_keyword("TIME_SLICE");
34223        self.write("(");
34224        self.generate_expression(&e.this)?;
34225        self.write(", ");
34226        self.generate_expression(&e.expression)?;
34227        self.write(", ");
34228        self.write_keyword(&e.unit);
34229        self.write(")");
34230        Ok(())
34231    }
34232
34233    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34234        // TIME_STR_TO_TIME(this)
34235        self.write_keyword("TIME_STR_TO_TIME");
34236        self.write("(");
34237        self.generate_expression(&e.this)?;
34238        self.write(")");
34239        Ok(())
34240    }
34241
34242    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34243        // TIME_SUB(this, expression, unit)
34244        self.write_keyword("TIME_SUB");
34245        self.write("(");
34246        self.generate_expression(&e.this)?;
34247        self.write(", ");
34248        self.generate_expression(&e.expression)?;
34249        if let Some(unit) = &e.unit {
34250            self.write(", ");
34251            self.write_keyword(unit);
34252        }
34253        self.write(")");
34254        Ok(())
34255    }
34256
34257    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34258        match self.config.dialect {
34259            Some(DialectType::Exasol) => {
34260                // Exasol uses TO_CHAR with Exasol-specific format
34261                self.write_keyword("TO_CHAR");
34262                self.write("(");
34263                self.generate_expression(&e.this)?;
34264                self.write(", '");
34265                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34266                self.write("'");
34267                self.write(")");
34268            }
34269            Some(DialectType::PostgreSQL)
34270            | Some(DialectType::Redshift)
34271            | Some(DialectType::Materialize) => {
34272                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34273                self.write_keyword("TO_CHAR");
34274                self.write("(");
34275                self.generate_expression(&e.this)?;
34276                self.write(", '");
34277                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34278                self.write("'");
34279                self.write(")");
34280            }
34281            Some(DialectType::Oracle) => {
34282                // Oracle uses TO_CHAR with PG-like format
34283                self.write_keyword("TO_CHAR");
34284                self.write("(");
34285                self.generate_expression(&e.this)?;
34286                self.write(", '");
34287                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34288                self.write("'");
34289                self.write(")");
34290            }
34291            Some(DialectType::Drill) => {
34292                // Drill: TO_CHAR with Java format
34293                self.write_keyword("TO_CHAR");
34294                self.write("(");
34295                self.generate_expression(&e.this)?;
34296                self.write(", '");
34297                self.write(&Self::strftime_to_java_format(&e.format));
34298                self.write("'");
34299                self.write(")");
34300            }
34301            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34302                // TSQL: FORMAT(value, format) with .NET-style format
34303                self.write_keyword("FORMAT");
34304                self.write("(");
34305                self.generate_expression(&e.this)?;
34306                self.write(", '");
34307                self.write(&Self::strftime_to_tsql_format(&e.format));
34308                self.write("'");
34309                self.write(")");
34310            }
34311            Some(DialectType::DuckDB) => {
34312                // DuckDB: STRFTIME(value, format) - keeps C format
34313                self.write_keyword("STRFTIME");
34314                self.write("(");
34315                self.generate_expression(&e.this)?;
34316                self.write(", '");
34317                self.write(&e.format);
34318                self.write("'");
34319                self.write(")");
34320            }
34321            Some(DialectType::BigQuery) => {
34322                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34323                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34324                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34325                self.write_keyword("FORMAT_DATE");
34326                self.write("('");
34327                self.write(&fmt);
34328                self.write("', ");
34329                self.generate_expression(&e.this)?;
34330                self.write(")");
34331            }
34332            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34333                // Hive/Spark: DATE_FORMAT(value, java_format)
34334                self.write_keyword("DATE_FORMAT");
34335                self.write("(");
34336                self.generate_expression(&e.this)?;
34337                self.write(", '");
34338                self.write(&Self::strftime_to_java_format(&e.format));
34339                self.write("'");
34340                self.write(")");
34341            }
34342            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34343                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34344                self.write_keyword("DATE_FORMAT");
34345                self.write("(");
34346                self.generate_expression(&e.this)?;
34347                self.write(", '");
34348                self.write(&e.format);
34349                self.write("'");
34350                self.write(")");
34351            }
34352            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34353                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34354                self.write_keyword("DATE_FORMAT");
34355                self.write("(");
34356                self.generate_expression(&e.this)?;
34357                self.write(", '");
34358                self.write(&e.format);
34359                self.write("'");
34360                self.write(")");
34361            }
34362            _ => {
34363                // Default: TIME_TO_STR(this, format)
34364                self.write_keyword("TIME_TO_STR");
34365                self.write("(");
34366                self.generate_expression(&e.this)?;
34367                self.write(", '");
34368                self.write(&e.format);
34369                self.write("'");
34370                self.write(")");
34371            }
34372        }
34373        Ok(())
34374    }
34375
34376    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34377        match self.config.dialect {
34378            Some(DialectType::DuckDB) => {
34379                // DuckDB: EPOCH(x)
34380                self.write_keyword("EPOCH");
34381                self.write("(");
34382                self.generate_expression(&e.this)?;
34383                self.write(")");
34384            }
34385            Some(DialectType::Hive)
34386            | Some(DialectType::Spark)
34387            | Some(DialectType::Databricks)
34388            | Some(DialectType::Doris)
34389            | Some(DialectType::StarRocks)
34390            | Some(DialectType::Drill) => {
34391                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34392                self.write_keyword("UNIX_TIMESTAMP");
34393                self.write("(");
34394                self.generate_expression(&e.this)?;
34395                self.write(")");
34396            }
34397            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34398                // Presto: TO_UNIXTIME(x)
34399                self.write_keyword("TO_UNIXTIME");
34400                self.write("(");
34401                self.generate_expression(&e.this)?;
34402                self.write(")");
34403            }
34404            _ => {
34405                // Default: TIME_TO_UNIX(x)
34406                self.write_keyword("TIME_TO_UNIX");
34407                self.write("(");
34408                self.generate_expression(&e.this)?;
34409                self.write(")");
34410            }
34411        }
34412        Ok(())
34413    }
34414
34415    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34416        match self.config.dialect {
34417            Some(DialectType::Hive) => {
34418                // Hive: TO_DATE(x)
34419                self.write_keyword("TO_DATE");
34420                self.write("(");
34421                self.generate_expression(&e.this)?;
34422                self.write(")");
34423            }
34424            _ => {
34425                // Default: TIME_STR_TO_DATE(x)
34426                self.write_keyword("TIME_STR_TO_DATE");
34427                self.write("(");
34428                self.generate_expression(&e.this)?;
34429                self.write(")");
34430            }
34431        }
34432        Ok(())
34433    }
34434
34435    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34436        // TIME_TRUNC(this, unit)
34437        self.write_keyword("TIME_TRUNC");
34438        self.write("(");
34439        self.generate_expression(&e.this)?;
34440        self.write(", ");
34441        self.write_keyword(&e.unit);
34442        self.write(")");
34443        Ok(())
34444    }
34445
34446    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34447        // Just output the unit name
34448        if let Some(unit) = &e.unit {
34449            self.write_keyword(unit);
34450        }
34451        Ok(())
34452    }
34453
34454    /// Generate a Timestamp function expression
34455    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34456    /// For other dialects: TIMESTAMP('value')
34457    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34458        use crate::dialects::DialectType;
34459        use crate::expressions::Literal;
34460
34461        match self.config.dialect {
34462            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34463            Some(DialectType::Exasol) => {
34464                self.write_keyword("TO_TIMESTAMP");
34465                self.write("(");
34466                // Extract the string value from the expression if it's a string literal
34467                if let Some(this) = &e.this {
34468                    match this.as_ref() {
34469                        Expression::Literal(Literal::String(s)) => {
34470                            self.write("'");
34471                            self.write(s);
34472                            self.write("'");
34473                        }
34474                        _ => {
34475                            self.generate_expression(this)?;
34476                        }
34477                    }
34478                }
34479                self.write(")");
34480            }
34481            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34482            _ => {
34483                self.write_keyword("TIMESTAMP");
34484                self.write("(");
34485                if let Some(this) = &e.this {
34486                    self.generate_expression(this)?;
34487                }
34488                if let Some(zone) = &e.zone {
34489                    self.write(", ");
34490                    self.generate_expression(zone)?;
34491                }
34492                self.write(")");
34493            }
34494        }
34495        Ok(())
34496    }
34497
34498    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34499        // TIMESTAMP_ADD(this, expression, unit)
34500        self.write_keyword("TIMESTAMP_ADD");
34501        self.write("(");
34502        self.generate_expression(&e.this)?;
34503        self.write(", ");
34504        self.generate_expression(&e.expression)?;
34505        if let Some(unit) = &e.unit {
34506            self.write(", ");
34507            self.write_keyword(unit);
34508        }
34509        self.write(")");
34510        Ok(())
34511    }
34512
34513    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34514        // TIMESTAMP_DIFF(this, expression, unit)
34515        self.write_keyword("TIMESTAMP_DIFF");
34516        self.write("(");
34517        self.generate_expression(&e.this)?;
34518        self.write(", ");
34519        self.generate_expression(&e.expression)?;
34520        if let Some(unit) = &e.unit {
34521            self.write(", ");
34522            self.write_keyword(unit);
34523        }
34524        self.write(")");
34525        Ok(())
34526    }
34527
34528    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34529        // TIMESTAMP_FROM_PARTS(this, expression)
34530        self.write_keyword("TIMESTAMP_FROM_PARTS");
34531        self.write("(");
34532        if let Some(this) = &e.this {
34533            self.generate_expression(this)?;
34534        }
34535        if let Some(expression) = &e.expression {
34536            self.write(", ");
34537            self.generate_expression(expression)?;
34538        }
34539        if let Some(zone) = &e.zone {
34540            self.write(", ");
34541            self.generate_expression(zone)?;
34542        }
34543        if let Some(milli) = &e.milli {
34544            self.write(", ");
34545            self.generate_expression(milli)?;
34546        }
34547        self.write(")");
34548        Ok(())
34549    }
34550
34551    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34552        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34553        self.write_keyword("TIMESTAMP_SUB");
34554        self.write("(");
34555        self.generate_expression(&e.this)?;
34556        self.write(", ");
34557        self.write_keyword("INTERVAL");
34558        self.write_space();
34559        self.generate_expression(&e.expression)?;
34560        if let Some(unit) = &e.unit {
34561            self.write_space();
34562            self.write_keyword(unit);
34563        }
34564        self.write(")");
34565        Ok(())
34566    }
34567
34568    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34569        // TIMESTAMP_TZ_FROM_PARTS(...)
34570        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34571        self.write("(");
34572        if let Some(zone) = &e.zone {
34573            self.generate_expression(zone)?;
34574        }
34575        self.write(")");
34576        Ok(())
34577    }
34578
34579    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34580        // TO_BINARY(this, [format])
34581        self.write_keyword("TO_BINARY");
34582        self.write("(");
34583        self.generate_expression(&e.this)?;
34584        if let Some(format) = &e.format {
34585            self.write(", '");
34586            self.write(format);
34587            self.write("'");
34588        }
34589        self.write(")");
34590        Ok(())
34591    }
34592
34593    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34594        // TO_BOOLEAN(this)
34595        self.write_keyword("TO_BOOLEAN");
34596        self.write("(");
34597        self.generate_expression(&e.this)?;
34598        self.write(")");
34599        Ok(())
34600    }
34601
34602    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34603        // TO_CHAR(this, [format], [nlsparam])
34604        self.write_keyword("TO_CHAR");
34605        self.write("(");
34606        self.generate_expression(&e.this)?;
34607        if let Some(format) = &e.format {
34608            self.write(", '");
34609            self.write(format);
34610            self.write("'");
34611        }
34612        if let Some(nlsparam) = &e.nlsparam {
34613            self.write(", ");
34614            self.generate_expression(nlsparam)?;
34615        }
34616        self.write(")");
34617        Ok(())
34618    }
34619
34620    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34621        // TO_DECFLOAT(this, [format])
34622        self.write_keyword("TO_DECFLOAT");
34623        self.write("(");
34624        self.generate_expression(&e.this)?;
34625        if let Some(format) = &e.format {
34626            self.write(", '");
34627            self.write(format);
34628            self.write("'");
34629        }
34630        self.write(")");
34631        Ok(())
34632    }
34633
34634    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34635        // TO_DOUBLE(this, [format])
34636        self.write_keyword("TO_DOUBLE");
34637        self.write("(");
34638        self.generate_expression(&e.this)?;
34639        if let Some(format) = &e.format {
34640            self.write(", '");
34641            self.write(format);
34642            self.write("'");
34643        }
34644        self.write(")");
34645        Ok(())
34646    }
34647
34648    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34649        // TO_FILE(this, path)
34650        self.write_keyword("TO_FILE");
34651        self.write("(");
34652        self.generate_expression(&e.this)?;
34653        if let Some(path) = &e.path {
34654            self.write(", ");
34655            self.generate_expression(path)?;
34656        }
34657        self.write(")");
34658        Ok(())
34659    }
34660
34661    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34662        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34663        // If safe flag is set, output TRY_TO_NUMBER
34664        let is_safe = e.safe.is_some();
34665        if is_safe {
34666            self.write_keyword("TRY_TO_NUMBER");
34667        } else {
34668            self.write_keyword("TO_NUMBER");
34669        }
34670        self.write("(");
34671        self.generate_expression(&e.this)?;
34672        if let Some(format) = &e.format {
34673            self.write(", ");
34674            self.generate_expression(format)?;
34675        }
34676        if let Some(nlsparam) = &e.nlsparam {
34677            self.write(", ");
34678            self.generate_expression(nlsparam)?;
34679        }
34680        if let Some(precision) = &e.precision {
34681            self.write(", ");
34682            self.generate_expression(precision)?;
34683        }
34684        if let Some(scale) = &e.scale {
34685            self.write(", ");
34686            self.generate_expression(scale)?;
34687        }
34688        self.write(")");
34689        Ok(())
34690    }
34691
34692    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34693        // TO_TABLE this
34694        self.write_keyword("TO_TABLE");
34695        self.write_space();
34696        self.generate_expression(&e.this)?;
34697        Ok(())
34698    }
34699
34700    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34701        // Check mark to determine the format
34702        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34703            Expression::Identifier(id) => id.name.clone(),
34704            Expression::Literal(Literal::String(s)) => s.clone(),
34705            _ => String::new(),
34706        });
34707
34708        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34709        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34710        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34711            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34712        });
34713
34714        // For Presto/Trino: always use START TRANSACTION
34715        let use_start_transaction = matches!(
34716            self.config.dialect,
34717            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34718        );
34719        // For most dialects: strip TRANSACTION keyword
34720        let strip_transaction = matches!(
34721            self.config.dialect,
34722            Some(DialectType::Snowflake)
34723                | Some(DialectType::PostgreSQL)
34724                | Some(DialectType::Redshift)
34725                | Some(DialectType::MySQL)
34726                | Some(DialectType::Hive)
34727                | Some(DialectType::Spark)
34728                | Some(DialectType::Databricks)
34729                | Some(DialectType::DuckDB)
34730                | Some(DialectType::Oracle)
34731                | Some(DialectType::Doris)
34732                | Some(DialectType::StarRocks)
34733                | Some(DialectType::Materialize)
34734                | Some(DialectType::ClickHouse)
34735        );
34736
34737        if is_start || use_start_transaction {
34738            // START TRANSACTION [modes]
34739            self.write_keyword("START TRANSACTION");
34740            if let Some(modes) = &e.modes {
34741                self.write_space();
34742                self.generate_expression(modes)?;
34743            }
34744        } else {
34745            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34746            self.write_keyword("BEGIN");
34747
34748            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34749            let is_kind = e.this.as_ref().map_or(false, |t| {
34750                if let Expression::Identifier(id) = t.as_ref() {
34751                    matches!(
34752                        id.name.to_uppercase().as_str(),
34753                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34754                    )
34755                } else {
34756                    false
34757                }
34758            });
34759
34760            // Output kind before TRANSACTION keyword
34761            if is_kind {
34762                if let Some(this) = &e.this {
34763                    self.write_space();
34764                    if let Expression::Identifier(id) = this.as_ref() {
34765                        self.write_keyword(&id.name);
34766                    }
34767                }
34768            }
34769
34770            // Output TRANSACTION keyword if it was present and target supports it
34771            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34772                self.write_space();
34773                self.write_keyword("TRANSACTION");
34774            }
34775
34776            // Output transaction name (not kind)
34777            if !is_kind {
34778                if let Some(this) = &e.this {
34779                    self.write_space();
34780                    self.generate_expression(this)?;
34781                }
34782            }
34783
34784            // Output WITH MARK 'description' for TSQL
34785            if has_with_mark {
34786                self.write_space();
34787                self.write_keyword("WITH MARK");
34788                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34789                    if !desc.is_empty() {
34790                        self.write_space();
34791                        self.write(&format!("'{}'", desc));
34792                    }
34793                }
34794            }
34795
34796            // Output modes (isolation levels, etc.)
34797            if let Some(modes) = &e.modes {
34798                self.write_space();
34799                self.generate_expression(modes)?;
34800            }
34801        }
34802        Ok(())
34803    }
34804
34805    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34806        // TRANSFORM(this, expression)
34807        self.write_keyword("TRANSFORM");
34808        self.write("(");
34809        self.generate_expression(&e.this)?;
34810        self.write(", ");
34811        self.generate_expression(&e.expression)?;
34812        self.write(")");
34813        Ok(())
34814    }
34815
34816    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34817        // TRANSFORM(expressions)
34818        self.write_keyword("TRANSFORM");
34819        self.write("(");
34820        if self.config.pretty && !e.expressions.is_empty() {
34821            self.indent_level += 1;
34822            for (i, expr) in e.expressions.iter().enumerate() {
34823                if i > 0 {
34824                    self.write(",");
34825                }
34826                self.write_newline();
34827                self.write_indent();
34828                self.generate_expression(expr)?;
34829            }
34830            self.indent_level -= 1;
34831            self.write_newline();
34832            self.write(")");
34833        } else {
34834            for (i, expr) in e.expressions.iter().enumerate() {
34835                if i > 0 {
34836                    self.write(", ");
34837                }
34838                self.generate_expression(expr)?;
34839            }
34840            self.write(")");
34841        }
34842        Ok(())
34843    }
34844
34845    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34846        use crate::dialects::DialectType;
34847        // TRANSIENT is Snowflake-specific; skip for other dialects
34848        if let Some(this) = &e.this {
34849            self.generate_expression(this)?;
34850            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34851                self.write_space();
34852            }
34853        }
34854        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34855            self.write_keyword("TRANSIENT");
34856        }
34857        Ok(())
34858    }
34859
34860    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34861        // TRANSLATE(this, from_, to)
34862        self.write_keyword("TRANSLATE");
34863        self.write("(");
34864        self.generate_expression(&e.this)?;
34865        if let Some(from) = &e.from_ {
34866            self.write(", ");
34867            self.generate_expression(from)?;
34868        }
34869        if let Some(to) = &e.to {
34870            self.write(", ");
34871            self.generate_expression(to)?;
34872        }
34873        self.write(")");
34874        Ok(())
34875    }
34876
34877    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34878        // TRANSLATE(this USING expression)
34879        self.write_keyword("TRANSLATE");
34880        self.write("(");
34881        self.generate_expression(&e.this)?;
34882        self.write_space();
34883        self.write_keyword("USING");
34884        self.write_space();
34885        self.generate_expression(&e.expression)?;
34886        if e.with_error.is_some() {
34887            self.write_space();
34888            self.write_keyword("WITH ERROR");
34889        }
34890        self.write(")");
34891        Ok(())
34892    }
34893
34894    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34895        // TRUNCATE TABLE table1, table2, ...
34896        self.write_keyword("TRUNCATE TABLE");
34897        self.write_space();
34898        for (i, expr) in e.expressions.iter().enumerate() {
34899            if i > 0 {
34900                self.write(", ");
34901            }
34902            self.generate_expression(expr)?;
34903        }
34904        Ok(())
34905    }
34906
34907    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34908        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34909        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34910        self.write("(");
34911        self.generate_expression(&e.this)?;
34912        if let Some(alphabet) = &e.alphabet {
34913            self.write(", ");
34914            self.generate_expression(alphabet)?;
34915        }
34916        self.write(")");
34917        Ok(())
34918    }
34919
34920    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34921        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34922        self.write_keyword("TRY_BASE64_DECODE_STRING");
34923        self.write("(");
34924        self.generate_expression(&e.this)?;
34925        if let Some(alphabet) = &e.alphabet {
34926            self.write(", ");
34927            self.generate_expression(alphabet)?;
34928        }
34929        self.write(")");
34930        Ok(())
34931    }
34932
34933    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34934        // TRY_TO_DECFLOAT(this, [format])
34935        self.write_keyword("TRY_TO_DECFLOAT");
34936        self.write("(");
34937        self.generate_expression(&e.this)?;
34938        if let Some(format) = &e.format {
34939            self.write(", '");
34940            self.write(format);
34941            self.write("'");
34942        }
34943        self.write(")");
34944        Ok(())
34945    }
34946
34947    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34948        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34949        self.write_keyword("TS_OR_DS_ADD");
34950        self.write("(");
34951        self.generate_expression(&e.this)?;
34952        self.write(", ");
34953        self.generate_expression(&e.expression)?;
34954        if let Some(unit) = &e.unit {
34955            self.write(", ");
34956            self.write_keyword(unit);
34957        }
34958        if let Some(return_type) = &e.return_type {
34959            self.write(", ");
34960            self.generate_expression(return_type)?;
34961        }
34962        self.write(")");
34963        Ok(())
34964    }
34965
34966    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34967        // TS_OR_DS_DIFF(this, expression, [unit])
34968        self.write_keyword("TS_OR_DS_DIFF");
34969        self.write("(");
34970        self.generate_expression(&e.this)?;
34971        self.write(", ");
34972        self.generate_expression(&e.expression)?;
34973        if let Some(unit) = &e.unit {
34974            self.write(", ");
34975            self.write_keyword(unit);
34976        }
34977        self.write(")");
34978        Ok(())
34979    }
34980
34981    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34982        let default_time_format = "%Y-%m-%d %H:%M:%S";
34983        let default_date_format = "%Y-%m-%d";
34984        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34985            f != default_time_format && f != default_date_format
34986        });
34987
34988        if has_non_default_format {
34989            // With non-default format: dialect-specific handling
34990            let fmt = e.format.as_ref().unwrap();
34991            match self.config.dialect {
34992                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34993                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34994                    // STR_TO_DATE is the MySQL-native form of StrToTime
34995                    let str_to_time = crate::expressions::StrToTime {
34996                        this: Box::new((*e.this).clone()),
34997                        format: fmt.clone(),
34998                        zone: None,
34999                        safe: None,
35000                        target_type: None,
35001                    };
35002                    self.generate_str_to_time(&str_to_time)?;
35003                }
35004                Some(DialectType::Hive)
35005                | Some(DialectType::Spark)
35006                | Some(DialectType::Databricks) => {
35007                    // Hive/Spark: TO_DATE(x, java_fmt)
35008                    self.write_keyword("TO_DATE");
35009                    self.write("(");
35010                    self.generate_expression(&e.this)?;
35011                    self.write(", '");
35012                    self.write(&Self::strftime_to_java_format(fmt));
35013                    self.write("')");
35014                }
35015                Some(DialectType::Snowflake) => {
35016                    // Snowflake: TO_DATE(x, snowflake_fmt)
35017                    self.write_keyword("TO_DATE");
35018                    self.write("(");
35019                    self.generate_expression(&e.this)?;
35020                    self.write(", '");
35021                    self.write(&Self::strftime_to_snowflake_format(fmt));
35022                    self.write("')");
35023                }
35024                Some(DialectType::Doris) => {
35025                    // Doris: TO_DATE(x) - ignores format
35026                    self.write_keyword("TO_DATE");
35027                    self.write("(");
35028                    self.generate_expression(&e.this)?;
35029                    self.write(")");
35030                }
35031                _ => {
35032                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
35033                    self.write_keyword("CAST");
35034                    self.write("(");
35035                    let str_to_time = crate::expressions::StrToTime {
35036                        this: Box::new((*e.this).clone()),
35037                        format: fmt.clone(),
35038                        zone: None,
35039                        safe: None,
35040                        target_type: None,
35041                    };
35042                    self.generate_str_to_time(&str_to_time)?;
35043                    self.write_keyword(" AS ");
35044                    self.write_keyword("DATE");
35045                    self.write(")");
35046                }
35047            }
35048        } else {
35049            // Without format (or default format): simple date conversion
35050            match self.config.dialect {
35051                Some(DialectType::MySQL)
35052                | Some(DialectType::SQLite)
35053                | Some(DialectType::StarRocks) => {
35054                    // MySQL/SQLite/StarRocks: DATE(x)
35055                    self.write_keyword("DATE");
35056                    self.write("(");
35057                    self.generate_expression(&e.this)?;
35058                    self.write(")");
35059                }
35060                Some(DialectType::Hive)
35061                | Some(DialectType::Spark)
35062                | Some(DialectType::Databricks)
35063                | Some(DialectType::Snowflake)
35064                | Some(DialectType::Doris) => {
35065                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
35066                    self.write_keyword("TO_DATE");
35067                    self.write("(");
35068                    self.generate_expression(&e.this)?;
35069                    self.write(")");
35070                }
35071                Some(DialectType::Presto)
35072                | Some(DialectType::Trino)
35073                | Some(DialectType::Athena) => {
35074                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
35075                    self.write_keyword("CAST");
35076                    self.write("(");
35077                    self.write_keyword("CAST");
35078                    self.write("(");
35079                    self.generate_expression(&e.this)?;
35080                    self.write_keyword(" AS ");
35081                    self.write_keyword("TIMESTAMP");
35082                    self.write(")");
35083                    self.write_keyword(" AS ");
35084                    self.write_keyword("DATE");
35085                    self.write(")");
35086                }
35087                Some(DialectType::ClickHouse) => {
35088                    // ClickHouse: CAST(x AS Nullable(DATE))
35089                    self.write_keyword("CAST");
35090                    self.write("(");
35091                    self.generate_expression(&e.this)?;
35092                    self.write_keyword(" AS ");
35093                    self.write("Nullable(DATE)");
35094                    self.write(")");
35095                }
35096                _ => {
35097                    // Default: CAST(x AS DATE)
35098                    self.write_keyword("CAST");
35099                    self.write("(");
35100                    self.generate_expression(&e.this)?;
35101                    self.write_keyword(" AS ");
35102                    self.write_keyword("DATE");
35103                    self.write(")");
35104                }
35105            }
35106        }
35107        Ok(())
35108    }
35109
35110    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
35111        // TS_OR_DS_TO_TIME(this, [format])
35112        self.write_keyword("TS_OR_DS_TO_TIME");
35113        self.write("(");
35114        self.generate_expression(&e.this)?;
35115        if let Some(format) = &e.format {
35116            self.write(", '");
35117            self.write(format);
35118            self.write("'");
35119        }
35120        self.write(")");
35121        Ok(())
35122    }
35123
35124    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
35125        // UNHEX(this, [expression])
35126        self.write_keyword("UNHEX");
35127        self.write("(");
35128        self.generate_expression(&e.this)?;
35129        if let Some(expression) = &e.expression {
35130            self.write(", ");
35131            self.generate_expression(expression)?;
35132        }
35133        self.write(")");
35134        Ok(())
35135    }
35136
35137    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
35138        // U&this [UESCAPE escape]
35139        self.write("U&");
35140        self.generate_expression(&e.this)?;
35141        if let Some(escape) = &e.escape {
35142            self.write_space();
35143            self.write_keyword("UESCAPE");
35144            self.write_space();
35145            self.generate_expression(escape)?;
35146        }
35147        Ok(())
35148    }
35149
35150    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
35151        // UNIFORM(this, expression, [gen], [seed])
35152        self.write_keyword("UNIFORM");
35153        self.write("(");
35154        self.generate_expression(&e.this)?;
35155        self.write(", ");
35156        self.generate_expression(&e.expression)?;
35157        if let Some(gen) = &e.gen {
35158            self.write(", ");
35159            self.generate_expression(gen)?;
35160        }
35161        if let Some(seed) = &e.seed {
35162            self.write(", ");
35163            self.generate_expression(seed)?;
35164        }
35165        self.write(")");
35166        Ok(())
35167    }
35168
35169    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
35170        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
35171        self.write_keyword("UNIQUE");
35172        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
35173        if e.nulls.is_some() {
35174            self.write(" NULLS NOT DISTINCT");
35175        }
35176        if let Some(this) = &e.this {
35177            self.write_space();
35178            self.generate_expression(this)?;
35179        }
35180        if let Some(index_type) = &e.index_type {
35181            self.write(" USING ");
35182            self.generate_expression(index_type)?;
35183        }
35184        if let Some(on_conflict) = &e.on_conflict {
35185            self.write_space();
35186            self.generate_expression(on_conflict)?;
35187        }
35188        for opt in &e.options {
35189            self.write_space();
35190            self.generate_expression(opt)?;
35191        }
35192        Ok(())
35193    }
35194
35195    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
35196        // UNIQUE KEY (expressions)
35197        self.write_keyword("UNIQUE KEY");
35198        self.write(" (");
35199        for (i, expr) in e.expressions.iter().enumerate() {
35200            if i > 0 {
35201                self.write(", ");
35202            }
35203            self.generate_expression(expr)?;
35204        }
35205        self.write(")");
35206        Ok(())
35207    }
35208
35209    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
35210        // ROLLUP (r1(col1, col2), r2(col1))
35211        self.write_keyword("ROLLUP");
35212        self.write(" (");
35213        for (i, index) in e.expressions.iter().enumerate() {
35214            if i > 0 {
35215                self.write(", ");
35216            }
35217            self.generate_identifier(&index.name)?;
35218            self.write("(");
35219            for (j, col) in index.expressions.iter().enumerate() {
35220                if j > 0 {
35221                    self.write(", ");
35222                }
35223                self.generate_identifier(col)?;
35224            }
35225            self.write(")");
35226        }
35227        self.write(")");
35228        Ok(())
35229    }
35230
35231    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35232        match self.config.dialect {
35233            Some(DialectType::DuckDB) => {
35234                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35235                self.write_keyword("STRFTIME");
35236                self.write("(");
35237                self.write_keyword("TO_TIMESTAMP");
35238                self.write("(");
35239                self.generate_expression(&e.this)?;
35240                self.write("), '");
35241                if let Some(format) = &e.format {
35242                    self.write(format);
35243                }
35244                self.write("')");
35245            }
35246            Some(DialectType::Hive) => {
35247                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35248                self.write_keyword("FROM_UNIXTIME");
35249                self.write("(");
35250                self.generate_expression(&e.this)?;
35251                if let Some(format) = &e.format {
35252                    if format != "yyyy-MM-dd HH:mm:ss" {
35253                        self.write(", '");
35254                        self.write(format);
35255                        self.write("'");
35256                    }
35257                }
35258                self.write(")");
35259            }
35260            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35261                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35262                self.write_keyword("DATE_FORMAT");
35263                self.write("(");
35264                self.write_keyword("FROM_UNIXTIME");
35265                self.write("(");
35266                self.generate_expression(&e.this)?;
35267                self.write("), '");
35268                if let Some(format) = &e.format {
35269                    self.write(format);
35270                }
35271                self.write("')");
35272            }
35273            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35274                // Spark: FROM_UNIXTIME(value, format)
35275                self.write_keyword("FROM_UNIXTIME");
35276                self.write("(");
35277                self.generate_expression(&e.this)?;
35278                if let Some(format) = &e.format {
35279                    self.write(", '");
35280                    self.write(format);
35281                    self.write("'");
35282                }
35283                self.write(")");
35284            }
35285            _ => {
35286                // Default: UNIX_TO_STR(this, [format])
35287                self.write_keyword("UNIX_TO_STR");
35288                self.write("(");
35289                self.generate_expression(&e.this)?;
35290                if let Some(format) = &e.format {
35291                    self.write(", '");
35292                    self.write(format);
35293                    self.write("'");
35294                }
35295                self.write(")");
35296            }
35297        }
35298        Ok(())
35299    }
35300
35301    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35302        use crate::dialects::DialectType;
35303        let scale = e.scale.unwrap_or(0); // 0 = seconds
35304
35305        match self.config.dialect {
35306            Some(DialectType::Snowflake) => {
35307                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35308                self.write_keyword("TO_TIMESTAMP");
35309                self.write("(");
35310                self.generate_expression(&e.this)?;
35311                if let Some(s) = e.scale {
35312                    if s > 0 {
35313                        self.write(", ");
35314                        self.write(&s.to_string());
35315                    }
35316                }
35317                self.write(")");
35318            }
35319            Some(DialectType::BigQuery) => {
35320                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35321                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35322                match scale {
35323                    0 => {
35324                        self.write_keyword("TIMESTAMP_SECONDS");
35325                        self.write("(");
35326                        self.generate_expression(&e.this)?;
35327                        self.write(")");
35328                    }
35329                    3 => {
35330                        self.write_keyword("TIMESTAMP_MILLIS");
35331                        self.write("(");
35332                        self.generate_expression(&e.this)?;
35333                        self.write(")");
35334                    }
35335                    6 => {
35336                        self.write_keyword("TIMESTAMP_MICROS");
35337                        self.write("(");
35338                        self.generate_expression(&e.this)?;
35339                        self.write(")");
35340                    }
35341                    _ => {
35342                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35343                        self.write_keyword("TIMESTAMP_SECONDS");
35344                        self.write("(CAST(");
35345                        self.generate_expression(&e.this)?;
35346                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35347                    }
35348                }
35349            }
35350            Some(DialectType::Spark) => {
35351                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35352                // TIMESTAMP_MILLIS(value) for scale=3
35353                // TIMESTAMP_MICROS(value) for scale=6
35354                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35355                match scale {
35356                    0 => {
35357                        self.write_keyword("CAST");
35358                        self.write("(");
35359                        self.write_keyword("FROM_UNIXTIME");
35360                        self.write("(");
35361                        self.generate_expression(&e.this)?;
35362                        self.write(") ");
35363                        self.write_keyword("AS TIMESTAMP");
35364                        self.write(")");
35365                    }
35366                    3 => {
35367                        self.write_keyword("TIMESTAMP_MILLIS");
35368                        self.write("(");
35369                        self.generate_expression(&e.this)?;
35370                        self.write(")");
35371                    }
35372                    6 => {
35373                        self.write_keyword("TIMESTAMP_MICROS");
35374                        self.write("(");
35375                        self.generate_expression(&e.this)?;
35376                        self.write(")");
35377                    }
35378                    _ => {
35379                        self.write_keyword("TIMESTAMP_SECONDS");
35380                        self.write("(");
35381                        self.generate_expression(&e.this)?;
35382                        self.write(&format!(" / POWER(10, {}))", scale));
35383                    }
35384                }
35385            }
35386            Some(DialectType::Databricks) => {
35387                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35388                // TIMESTAMP_MILLIS(value) for scale=3
35389                // TIMESTAMP_MICROS(value) for scale=6
35390                match scale {
35391                    0 => {
35392                        self.write_keyword("CAST");
35393                        self.write("(");
35394                        self.write_keyword("FROM_UNIXTIME");
35395                        self.write("(");
35396                        self.generate_expression(&e.this)?;
35397                        self.write(") ");
35398                        self.write_keyword("AS TIMESTAMP");
35399                        self.write(")");
35400                    }
35401                    3 => {
35402                        self.write_keyword("TIMESTAMP_MILLIS");
35403                        self.write("(");
35404                        self.generate_expression(&e.this)?;
35405                        self.write(")");
35406                    }
35407                    6 => {
35408                        self.write_keyword("TIMESTAMP_MICROS");
35409                        self.write("(");
35410                        self.generate_expression(&e.this)?;
35411                        self.write(")");
35412                    }
35413                    _ => {
35414                        self.write_keyword("TIMESTAMP_SECONDS");
35415                        self.write("(");
35416                        self.generate_expression(&e.this)?;
35417                        self.write(&format!(" / POWER(10, {}))", scale));
35418                    }
35419                }
35420            }
35421            Some(DialectType::Hive) => {
35422                // Hive: FROM_UNIXTIME(value)
35423                if scale == 0 {
35424                    self.write_keyword("FROM_UNIXTIME");
35425                    self.write("(");
35426                    self.generate_expression(&e.this)?;
35427                    self.write(")");
35428                } else {
35429                    self.write_keyword("FROM_UNIXTIME");
35430                    self.write("(");
35431                    self.generate_expression(&e.this)?;
35432                    self.write(&format!(" / POWER(10, {})", scale));
35433                    self.write(")");
35434                }
35435            }
35436            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35437                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35438                // FROM_UNIXTIME(value) for scale=0
35439                if scale == 0 {
35440                    self.write_keyword("FROM_UNIXTIME");
35441                    self.write("(");
35442                    self.generate_expression(&e.this)?;
35443                    self.write(")");
35444                } else {
35445                    self.write_keyword("FROM_UNIXTIME");
35446                    self.write("(CAST(");
35447                    self.generate_expression(&e.this)?;
35448                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35449                }
35450            }
35451            Some(DialectType::DuckDB) => {
35452                // DuckDB: TO_TIMESTAMP(value) for scale=0
35453                // EPOCH_MS(value) for scale=3
35454                // MAKE_TIMESTAMP(value) for scale=6
35455                match scale {
35456                    0 => {
35457                        self.write_keyword("TO_TIMESTAMP");
35458                        self.write("(");
35459                        self.generate_expression(&e.this)?;
35460                        self.write(")");
35461                    }
35462                    3 => {
35463                        self.write_keyword("EPOCH_MS");
35464                        self.write("(");
35465                        self.generate_expression(&e.this)?;
35466                        self.write(")");
35467                    }
35468                    6 => {
35469                        self.write_keyword("MAKE_TIMESTAMP");
35470                        self.write("(");
35471                        self.generate_expression(&e.this)?;
35472                        self.write(")");
35473                    }
35474                    _ => {
35475                        self.write_keyword("TO_TIMESTAMP");
35476                        self.write("(");
35477                        self.generate_expression(&e.this)?;
35478                        self.write(&format!(" / POWER(10, {}))", scale));
35479                        self.write_keyword(" AT TIME ZONE");
35480                        self.write(" 'UTC'");
35481                    }
35482                }
35483            }
35484            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35485                // Doris/StarRocks: FROM_UNIXTIME(value)
35486                self.write_keyword("FROM_UNIXTIME");
35487                self.write("(");
35488                self.generate_expression(&e.this)?;
35489                self.write(")");
35490            }
35491            Some(DialectType::Oracle) => {
35492                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35493                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35494                self.generate_expression(&e.this)?;
35495                self.write(" / 86400)");
35496            }
35497            Some(DialectType::Redshift) => {
35498                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35499                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35500                self.write("(TIMESTAMP 'epoch' + ");
35501                if scale == 0 {
35502                    self.generate_expression(&e.this)?;
35503                } else {
35504                    self.write("(");
35505                    self.generate_expression(&e.this)?;
35506                    self.write(&format!(" / POWER(10, {}))", scale));
35507                }
35508                self.write(" * INTERVAL '1 SECOND')");
35509            }
35510            _ => {
35511                // Default: TO_TIMESTAMP(value[, scale])
35512                self.write_keyword("TO_TIMESTAMP");
35513                self.write("(");
35514                self.generate_expression(&e.this)?;
35515                if let Some(s) = e.scale {
35516                    self.write(", ");
35517                    self.write(&s.to_string());
35518                }
35519                self.write(")");
35520            }
35521        }
35522        Ok(())
35523    }
35524
35525    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35526        // NAME col VALUE col1, col2, ...
35527        if !matches!(&*e.this, Expression::Null(_)) {
35528            self.write_keyword("NAME");
35529            self.write_space();
35530            self.generate_expression(&e.this)?;
35531        }
35532        if !e.expressions.is_empty() {
35533            self.write_space();
35534            self.write_keyword("VALUE");
35535            self.write_space();
35536            for (i, expr) in e.expressions.iter().enumerate() {
35537                if i > 0 {
35538                    self.write(", ");
35539                }
35540                self.generate_expression(expr)?;
35541            }
35542        }
35543        Ok(())
35544    }
35545
35546    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35547        // this(expressions) or (this)(expressions)
35548        if e.wrapped.is_some() {
35549            self.write("(");
35550        }
35551        self.generate_expression(&e.this)?;
35552        if e.wrapped.is_some() {
35553            self.write(")");
35554        }
35555        self.write("(");
35556        for (i, expr) in e.expressions.iter().enumerate() {
35557            if i > 0 {
35558                self.write(", ");
35559            }
35560            self.generate_expression(expr)?;
35561        }
35562        self.write(")");
35563        Ok(())
35564    }
35565
35566    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35567        // USING TEMPLATE this
35568        self.write_keyword("USING TEMPLATE");
35569        self.write_space();
35570        self.generate_expression(&e.this)?;
35571        Ok(())
35572    }
35573
35574    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35575        // UTC_TIME
35576        self.write_keyword("UTC_TIME");
35577        Ok(())
35578    }
35579
35580    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35581        // UTC_TIMESTAMP
35582        self.write_keyword("UTC_TIMESTAMP");
35583        Ok(())
35584    }
35585
35586    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35587        use crate::dialects::DialectType;
35588        // Choose UUID function name based on target dialect
35589        let func_name = match self.config.dialect {
35590            Some(DialectType::Snowflake) => "UUID_STRING",
35591            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35592            Some(DialectType::BigQuery) => "GENERATE_UUID",
35593            _ => {
35594                if let Some(name) = &e.name {
35595                    name.as_str()
35596                } else {
35597                    "UUID"
35598                }
35599            }
35600        };
35601        self.write_keyword(func_name);
35602        self.write("(");
35603        if let Some(this) = &e.this {
35604            self.generate_expression(this)?;
35605        }
35606        self.write(")");
35607        Ok(())
35608    }
35609
35610    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35611        // MAP(key1, value1, key2, value2, ...)
35612        self.write_keyword("MAP");
35613        self.write("(");
35614        let mut first = true;
35615        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35616            if !first {
35617                self.write(", ");
35618            }
35619            self.generate_expression(k)?;
35620            self.write(", ");
35621            self.generate_expression(v)?;
35622            first = false;
35623        }
35624        self.write(")");
35625        Ok(())
35626    }
35627
35628    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35629        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35630        self.write_keyword("VECTOR_SEARCH");
35631        self.write("(");
35632        self.generate_expression(&e.this)?;
35633        if let Some(col) = &e.column_to_search {
35634            self.write(", ");
35635            self.generate_expression(col)?;
35636        }
35637        if let Some(query_table) = &e.query_table {
35638            self.write(", ");
35639            self.generate_expression(query_table)?;
35640        }
35641        if let Some(query_col) = &e.query_column_to_search {
35642            self.write(", ");
35643            self.generate_expression(query_col)?;
35644        }
35645        if let Some(top_k) = &e.top_k {
35646            self.write(", ");
35647            self.generate_expression(top_k)?;
35648        }
35649        if let Some(dist_type) = &e.distance_type {
35650            self.write(", ");
35651            self.generate_expression(dist_type)?;
35652        }
35653        self.write(")");
35654        Ok(())
35655    }
35656
35657    fn generate_version(&mut self, e: &Version) -> Result<()> {
35658        // Python: f"FOR {expression.name} {kind} {expr}"
35659        // e.this = Identifier("TIMESTAMP" or "VERSION")
35660        // e.kind = "AS OF" (or "BETWEEN", etc.)
35661        // e.expression = the value expression
35662        // Hive does NOT use the FOR prefix for time travel
35663        use crate::dialects::DialectType;
35664        let skip_for = matches!(
35665            self.config.dialect,
35666            Some(DialectType::Hive) | Some(DialectType::Spark)
35667        );
35668        if !skip_for {
35669            self.write_keyword("FOR");
35670            self.write_space();
35671        }
35672        // Extract the name from this (which is an Identifier expression)
35673        match e.this.as_ref() {
35674            Expression::Identifier(ident) => {
35675                self.write_keyword(&ident.name);
35676            }
35677            _ => {
35678                self.generate_expression(&e.this)?;
35679            }
35680        }
35681        self.write_space();
35682        self.write_keyword(&e.kind);
35683        if let Some(expression) = &e.expression {
35684            self.write_space();
35685            self.generate_expression(expression)?;
35686        }
35687        Ok(())
35688    }
35689
35690    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35691        // Python: return self.sql(expression, "this")
35692        self.generate_expression(&e.this)?;
35693        Ok(())
35694    }
35695
35696    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35697        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35698        if e.this.is_some() {
35699            self.write_keyword("NOT VOLATILE");
35700        } else {
35701            self.write_keyword("VOLATILE");
35702        }
35703        Ok(())
35704    }
35705
35706    fn generate_watermark_column_constraint(
35707        &mut self,
35708        e: &WatermarkColumnConstraint,
35709    ) -> Result<()> {
35710        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35711        self.write_keyword("WATERMARK FOR");
35712        self.write_space();
35713        self.generate_expression(&e.this)?;
35714        self.write_space();
35715        self.write_keyword("AS");
35716        self.write_space();
35717        self.generate_expression(&e.expression)?;
35718        Ok(())
35719    }
35720
35721    fn generate_week(&mut self, e: &Week) -> Result<()> {
35722        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35723        self.write_keyword("WEEK");
35724        self.write("(");
35725        self.generate_expression(&e.this)?;
35726        if let Some(mode) = &e.mode {
35727            self.write(", ");
35728            self.generate_expression(mode)?;
35729        }
35730        self.write(")");
35731        Ok(())
35732    }
35733
35734    fn generate_when(&mut self, e: &When) -> Result<()> {
35735        // Python: WHEN {matched}{source}{condition} THEN {then}
35736        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35737        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35738        self.write_keyword("WHEN");
35739        self.write_space();
35740
35741        // Check if matched
35742        if let Some(matched) = &e.matched {
35743            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35744            match matched.as_ref() {
35745                Expression::Boolean(b) if b.value => {
35746                    self.write_keyword("MATCHED");
35747                }
35748                _ => {
35749                    self.write_keyword("NOT MATCHED");
35750                }
35751            }
35752        } else {
35753            self.write_keyword("NOT MATCHED");
35754        }
35755
35756        // BY SOURCE / BY TARGET
35757        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35758        // BY TARGET is the default and typically omitted in output
35759        // Only emit if the dialect supports BY SOURCE syntax
35760        if self.config.matched_by_source {
35761            if let Some(source) = &e.source {
35762                if let Expression::Boolean(b) = source.as_ref() {
35763                    if b.value {
35764                        // BY SOURCE
35765                        self.write_space();
35766                        self.write_keyword("BY SOURCE");
35767                    }
35768                    // BY TARGET (b.value == false) is omitted as it's the default
35769                } else {
35770                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35771                    self.write_space();
35772                    self.write_keyword("BY SOURCE");
35773                }
35774            }
35775        }
35776
35777        // Condition
35778        if let Some(condition) = &e.condition {
35779            self.write_space();
35780            self.write_keyword("AND");
35781            self.write_space();
35782            self.generate_expression(condition)?;
35783        }
35784
35785        self.write_space();
35786        self.write_keyword("THEN");
35787        self.write_space();
35788
35789        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35790        // MERGE actions are stored as Tuples with the action keyword as first element
35791        self.generate_merge_action(&e.then)?;
35792
35793        Ok(())
35794    }
35795
35796    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35797        match action {
35798            Expression::Tuple(tuple) => {
35799                let elements = &tuple.expressions;
35800                if elements.is_empty() {
35801                    return self.generate_expression(action);
35802                }
35803                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35804                match &elements[0] {
35805                    Expression::Var(v) if v.this == "INSERT" => {
35806                        self.write_keyword("INSERT");
35807                        // Spark: INSERT * (insert all columns)
35808                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35809                            self.write(" *");
35810                        } else {
35811                            let mut values_idx = 1;
35812                            // Check if second element is column list (Tuple)
35813                            if elements.len() > 1 {
35814                                if let Expression::Tuple(cols) = &elements[1] {
35815                                    // Could be columns or values - if there's a third element, second is columns
35816                                    if elements.len() > 2 {
35817                                        // Second is columns, third is values
35818                                        self.write(" (");
35819                                        for (i, col) in cols.expressions.iter().enumerate() {
35820                                            if i > 0 {
35821                                                self.write(", ");
35822                                            }
35823                                            // Strip MERGE target qualifiers from INSERT column list
35824                                            if !self.merge_strip_qualifiers.is_empty() {
35825                                                let stripped = self.strip_merge_qualifier(col);
35826                                                self.generate_expression(&stripped)?;
35827                                            } else {
35828                                                self.generate_expression(col)?;
35829                                            }
35830                                        }
35831                                        self.write(")");
35832                                        values_idx = 2;
35833                                    } else {
35834                                        // Only two elements: INSERT + values (no explicit columns)
35835                                        values_idx = 1;
35836                                    }
35837                                }
35838                            }
35839                            // Generate VALUES clause
35840                            if values_idx < elements.len() {
35841                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35842                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35843                                if !is_row {
35844                                    self.write_space();
35845                                    self.write_keyword("VALUES");
35846                                }
35847                                self.write(" ");
35848                                if let Expression::Tuple(vals) = &elements[values_idx] {
35849                                    self.write("(");
35850                                    for (i, val) in vals.expressions.iter().enumerate() {
35851                                        if i > 0 {
35852                                            self.write(", ");
35853                                        }
35854                                        self.generate_expression(val)?;
35855                                    }
35856                                    self.write(")");
35857                                } else {
35858                                    self.generate_expression(&elements[values_idx])?;
35859                                }
35860                            }
35861                        } // close else for INSERT * check
35862                    }
35863                    Expression::Var(v) if v.this == "UPDATE" => {
35864                        self.write_keyword("UPDATE");
35865                        // Spark: UPDATE * (update all columns)
35866                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35867                            self.write(" *");
35868                        } else if elements.len() > 1 {
35869                            self.write_space();
35870                            self.write_keyword("SET");
35871                            // In pretty mode, put assignments on next line with extra indent
35872                            if self.config.pretty {
35873                                self.write_newline();
35874                                self.indent_level += 1;
35875                                self.write_indent();
35876                            } else {
35877                                self.write_space();
35878                            }
35879                            if let Expression::Tuple(assignments) = &elements[1] {
35880                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35881                                    if i > 0 {
35882                                        if self.config.pretty {
35883                                            self.write(",");
35884                                            self.write_newline();
35885                                            self.write_indent();
35886                                        } else {
35887                                            self.write(", ");
35888                                        }
35889                                    }
35890                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35891                                    if !self.merge_strip_qualifiers.is_empty() {
35892                                        self.generate_merge_set_assignment(assignment)?;
35893                                    } else {
35894                                        self.generate_expression(assignment)?;
35895                                    }
35896                                }
35897                            } else {
35898                                self.generate_expression(&elements[1])?;
35899                            }
35900                            if self.config.pretty {
35901                                self.indent_level -= 1;
35902                            }
35903                        }
35904                    }
35905                    _ => {
35906                        // Fallback: generic tuple generation
35907                        self.generate_expression(action)?;
35908                    }
35909                }
35910            }
35911            Expression::Var(v)
35912                if v.this == "INSERT"
35913                    || v.this == "UPDATE"
35914                    || v.this == "DELETE"
35915                    || v.this == "DO NOTHING" =>
35916            {
35917                self.write_keyword(&v.this);
35918            }
35919            _ => {
35920                self.generate_expression(action)?;
35921            }
35922        }
35923        Ok(())
35924    }
35925
35926    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35927    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35928        match assignment {
35929            Expression::Eq(eq) => {
35930                // Strip qualifier from the left side if it matches a MERGE target name
35931                let stripped_left = self.strip_merge_qualifier(&eq.left);
35932                self.generate_expression(&stripped_left)?;
35933                self.write(" = ");
35934                self.generate_expression(&eq.right)?;
35935                Ok(())
35936            }
35937            other => self.generate_expression(other),
35938        }
35939    }
35940
35941    /// Strip table qualifier from a column reference if it matches a MERGE target name
35942    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35943        match expr {
35944            Expression::Column(col) => {
35945                if let Some(ref table_ident) = col.table {
35946                    if self
35947                        .merge_strip_qualifiers
35948                        .iter()
35949                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35950                    {
35951                        // Strip the table qualifier
35952                        let mut col = col.clone();
35953                        col.table = None;
35954                        return Expression::Column(col);
35955                    }
35956                }
35957                expr.clone()
35958            }
35959            Expression::Dot(dot) => {
35960                // table.column -> column (strip qualifier)
35961                if let Expression::Identifier(id) = &dot.this {
35962                    if self
35963                        .merge_strip_qualifiers
35964                        .iter()
35965                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35966                    {
35967                        return Expression::Identifier(dot.field.clone());
35968                    }
35969                }
35970                expr.clone()
35971            }
35972            _ => expr.clone(),
35973        }
35974    }
35975
35976    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35977        // Python: return self.expressions(expression, sep=" ", indent=False)
35978        for (i, expr) in e.expressions.iter().enumerate() {
35979            if i > 0 {
35980                // In pretty mode, each WHEN clause on its own line
35981                if self.config.pretty {
35982                    self.write_newline();
35983                    self.write_indent();
35984                } else {
35985                    self.write_space();
35986                }
35987            }
35988            self.generate_expression(expr)?;
35989        }
35990        Ok(())
35991    }
35992
35993    fn generate_where(&mut self, e: &Where) -> Result<()> {
35994        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35995        self.write_keyword("WHERE");
35996        self.write_space();
35997        self.generate_expression(&e.this)?;
35998        Ok(())
35999    }
36000
36001    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
36002        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
36003        self.write_keyword("WIDTH_BUCKET");
36004        self.write("(");
36005        self.generate_expression(&e.this)?;
36006        if let Some(min_value) = &e.min_value {
36007            self.write(", ");
36008            self.generate_expression(min_value)?;
36009        }
36010        if let Some(max_value) = &e.max_value {
36011            self.write(", ");
36012            self.generate_expression(max_value)?;
36013        }
36014        if let Some(num_buckets) = &e.num_buckets {
36015            self.write(", ");
36016            self.generate_expression(num_buckets)?;
36017        }
36018        self.write(")");
36019        Ok(())
36020    }
36021
36022    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
36023        // Window specification: PARTITION BY ... ORDER BY ... frame
36024        self.generate_window_spec(e)
36025    }
36026
36027    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
36028        // Window specification: PARTITION BY ... ORDER BY ... frame
36029        let mut has_content = false;
36030
36031        // PARTITION BY
36032        if !e.partition_by.is_empty() {
36033            self.write_keyword("PARTITION BY");
36034            self.write_space();
36035            for (i, expr) in e.partition_by.iter().enumerate() {
36036                if i > 0 {
36037                    self.write(", ");
36038                }
36039                self.generate_expression(expr)?;
36040            }
36041            has_content = true;
36042        }
36043
36044        // ORDER BY
36045        if !e.order_by.is_empty() {
36046            if has_content {
36047                self.write_space();
36048            }
36049            self.write_keyword("ORDER BY");
36050            self.write_space();
36051            for (i, ordered) in e.order_by.iter().enumerate() {
36052                if i > 0 {
36053                    self.write(", ");
36054                }
36055                self.generate_expression(&ordered.this)?;
36056                if ordered.desc {
36057                    self.write_space();
36058                    self.write_keyword("DESC");
36059                } else if ordered.explicit_asc {
36060                    self.write_space();
36061                    self.write_keyword("ASC");
36062                }
36063                if let Some(nulls_first) = ordered.nulls_first {
36064                    self.write_space();
36065                    self.write_keyword("NULLS");
36066                    self.write_space();
36067                    if nulls_first {
36068                        self.write_keyword("FIRST");
36069                    } else {
36070                        self.write_keyword("LAST");
36071                    }
36072                }
36073            }
36074            has_content = true;
36075        }
36076
36077        // Frame specification
36078        if let Some(frame) = &e.frame {
36079            if has_content {
36080                self.write_space();
36081            }
36082            self.generate_window_frame(frame)?;
36083        }
36084
36085        Ok(())
36086    }
36087
36088    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
36089        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
36090        self.write_keyword("WITH");
36091        self.write_space();
36092        if e.no.is_some() {
36093            self.write_keyword("NO");
36094            self.write_space();
36095        }
36096        self.write_keyword("DATA");
36097
36098        // statistics
36099        if let Some(statistics) = &e.statistics {
36100            self.write_space();
36101            self.write_keyword("AND");
36102            self.write_space();
36103            // Check if statistics is true or false
36104            match statistics.as_ref() {
36105                Expression::Boolean(b) if !b.value => {
36106                    self.write_keyword("NO");
36107                    self.write_space();
36108                }
36109                _ => {}
36110            }
36111            self.write_keyword("STATISTICS");
36112        }
36113        Ok(())
36114    }
36115
36116    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
36117        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
36118        self.write_keyword("WITH FILL");
36119
36120        if let Some(from_) = &e.from_ {
36121            self.write_space();
36122            self.write_keyword("FROM");
36123            self.write_space();
36124            self.generate_expression(from_)?;
36125        }
36126
36127        if let Some(to) = &e.to {
36128            self.write_space();
36129            self.write_keyword("TO");
36130            self.write_space();
36131            self.generate_expression(to)?;
36132        }
36133
36134        if let Some(step) = &e.step {
36135            self.write_space();
36136            self.write_keyword("STEP");
36137            self.write_space();
36138            self.generate_expression(step)?;
36139        }
36140
36141        if let Some(staleness) = &e.staleness {
36142            self.write_space();
36143            self.write_keyword("STALENESS");
36144            self.write_space();
36145            self.generate_expression(staleness)?;
36146        }
36147
36148        if let Some(interpolate) = &e.interpolate {
36149            self.write_space();
36150            self.write_keyword("INTERPOLATE");
36151            self.write(" (");
36152            // INTERPOLATE items use reversed alias format: name AS expression
36153            self.generate_interpolate_item(interpolate)?;
36154            self.write(")");
36155        }
36156
36157        Ok(())
36158    }
36159
36160    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
36161    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
36162        match expr {
36163            Expression::Alias(alias) => {
36164                // Output as: alias_name AS expression
36165                self.generate_identifier(&alias.alias)?;
36166                self.write_space();
36167                self.write_keyword("AS");
36168                self.write_space();
36169                self.generate_expression(&alias.this)?;
36170            }
36171            Expression::Tuple(tuple) => {
36172                for (i, item) in tuple.expressions.iter().enumerate() {
36173                    if i > 0 {
36174                        self.write(", ");
36175                    }
36176                    self.generate_interpolate_item(item)?;
36177                }
36178            }
36179            other => {
36180                self.generate_expression(other)?;
36181            }
36182        }
36183        Ok(())
36184    }
36185
36186    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
36187        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
36188        self.write_keyword("WITH JOURNAL TABLE");
36189        self.write("=");
36190        self.generate_expression(&e.this)?;
36191        Ok(())
36192    }
36193
36194    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
36195        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
36196        self.generate_expression(&e.this)?;
36197        self.write_space();
36198        self.write_keyword("WITH");
36199        self.write_space();
36200        self.write_keyword(&e.op);
36201        Ok(())
36202    }
36203
36204    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
36205        // Python: return f"WITH {self.expressions(expression, flat=True)}"
36206        self.write_keyword("WITH");
36207        self.write_space();
36208        for (i, expr) in e.expressions.iter().enumerate() {
36209            if i > 0 {
36210                self.write(", ");
36211            }
36212            self.generate_expression(expr)?;
36213        }
36214        Ok(())
36215    }
36216
36217    fn generate_with_schema_binding_property(
36218        &mut self,
36219        e: &WithSchemaBindingProperty,
36220    ) -> Result<()> {
36221        // Python: return f"WITH {self.sql(expression, 'this')}"
36222        self.write_keyword("WITH");
36223        self.write_space();
36224        self.generate_expression(&e.this)?;
36225        Ok(())
36226    }
36227
36228    fn generate_with_system_versioning_property(
36229        &mut self,
36230        e: &WithSystemVersioningProperty,
36231    ) -> Result<()> {
36232        // Python: complex logic for SYSTEM_VERSIONING with options
36233        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36234        // or SYSTEM_VERSIONING=ON/OFF
36235        // with WITH(...) wrapper if with_ is set
36236
36237        let mut parts = Vec::new();
36238
36239        if let Some(this) = &e.this {
36240            // HISTORY_TABLE=...
36241            let mut s = String::from("HISTORY_TABLE=");
36242            let mut gen = Generator::new();
36243            gen.generate_expression(this)?;
36244            s.push_str(&gen.output);
36245            parts.push(s);
36246        }
36247
36248        if let Some(data_consistency) = &e.data_consistency {
36249            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36250            let mut gen = Generator::new();
36251            gen.generate_expression(data_consistency)?;
36252            s.push_str(&gen.output);
36253            parts.push(s);
36254        }
36255
36256        if let Some(retention_period) = &e.retention_period {
36257            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36258            let mut gen = Generator::new();
36259            gen.generate_expression(retention_period)?;
36260            s.push_str(&gen.output);
36261            parts.push(s);
36262        }
36263
36264        self.write_keyword("SYSTEM_VERSIONING");
36265        self.write("=");
36266
36267        if !parts.is_empty() {
36268            self.write_keyword("ON");
36269            self.write("(");
36270            self.write(&parts.join(", "));
36271            self.write(")");
36272        } else if e.on.is_some() {
36273            self.write_keyword("ON");
36274        } else {
36275            self.write_keyword("OFF");
36276        }
36277
36278        // Wrap in WITH(...) if with_ is set
36279        if e.with_.is_some() {
36280            let inner = self.output.clone();
36281            self.output.clear();
36282            self.write("WITH(");
36283            self.write(&inner);
36284            self.write(")");
36285        }
36286
36287        Ok(())
36288    }
36289
36290    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36291        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36292        self.write_keyword("WITH");
36293        self.write(" (");
36294        for (i, expr) in e.expressions.iter().enumerate() {
36295            if i > 0 {
36296                self.write(", ");
36297            }
36298            self.generate_expression(expr)?;
36299        }
36300        self.write(")");
36301        Ok(())
36302    }
36303
36304    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36305        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36306        // return self.func("XMLELEMENT", name, *expression.expressions)
36307        self.write_keyword("XMLELEMENT");
36308        self.write("(");
36309
36310        if e.evalname.is_some() {
36311            self.write_keyword("EVALNAME");
36312        } else {
36313            self.write_keyword("NAME");
36314        }
36315        self.write_space();
36316        self.generate_expression(&e.this)?;
36317
36318        for expr in &e.expressions {
36319            self.write(", ");
36320            self.generate_expression(expr)?;
36321        }
36322        self.write(")");
36323        Ok(())
36324    }
36325
36326    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36327        // XMLGET(this, expression [, instance])
36328        self.write_keyword("XMLGET");
36329        self.write("(");
36330        self.generate_expression(&e.this)?;
36331        self.write(", ");
36332        self.generate_expression(&e.expression)?;
36333        if let Some(instance) = &e.instance {
36334            self.write(", ");
36335            self.generate_expression(instance)?;
36336        }
36337        self.write(")");
36338        Ok(())
36339    }
36340
36341    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36342        // Python: this + optional (expr)
36343        self.generate_expression(&e.this)?;
36344        if let Some(expression) = &e.expression {
36345            self.write("(");
36346            self.generate_expression(expression)?;
36347            self.write(")");
36348        }
36349        Ok(())
36350    }
36351
36352    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36353        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36354        self.write_keyword("XMLTABLE");
36355        self.write("(");
36356
36357        if self.config.pretty {
36358            self.indent_level += 1;
36359            self.write_newline();
36360            self.write_indent();
36361            self.generate_expression(&e.this)?;
36362
36363            if let Some(passing) = &e.passing {
36364                self.write_newline();
36365                self.write_indent();
36366                self.write_keyword("PASSING");
36367                if let Expression::Tuple(tuple) = passing.as_ref() {
36368                    for expr in &tuple.expressions {
36369                        self.write_newline();
36370                        self.indent_level += 1;
36371                        self.write_indent();
36372                        self.generate_expression(expr)?;
36373                        self.indent_level -= 1;
36374                    }
36375                } else {
36376                    self.write_newline();
36377                    self.indent_level += 1;
36378                    self.write_indent();
36379                    self.generate_expression(passing)?;
36380                    self.indent_level -= 1;
36381                }
36382            }
36383
36384            if e.by_ref.is_some() {
36385                self.write_newline();
36386                self.write_indent();
36387                self.write_keyword("RETURNING SEQUENCE BY REF");
36388            }
36389
36390            if !e.columns.is_empty() {
36391                self.write_newline();
36392                self.write_indent();
36393                self.write_keyword("COLUMNS");
36394                for (i, col) in e.columns.iter().enumerate() {
36395                    self.write_newline();
36396                    self.indent_level += 1;
36397                    self.write_indent();
36398                    self.generate_expression(col)?;
36399                    self.indent_level -= 1;
36400                    if i < e.columns.len() - 1 {
36401                        self.write(",");
36402                    }
36403                }
36404            }
36405
36406            self.indent_level -= 1;
36407            self.write_newline();
36408            self.write_indent();
36409            self.write(")");
36410            return Ok(());
36411        }
36412
36413        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36414        if let Some(namespaces) = &e.namespaces {
36415            self.write_keyword("XMLNAMESPACES");
36416            self.write("(");
36417            // Unwrap Tuple if present to avoid extra parentheses
36418            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36419                for (i, expr) in tuple.expressions.iter().enumerate() {
36420                    if i > 0 {
36421                        self.write(", ");
36422                    }
36423                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36424                    // See xmlnamespace_sql in generator.py
36425                    if !matches!(expr, Expression::Alias(_)) {
36426                        self.write_keyword("DEFAULT");
36427                        self.write_space();
36428                    }
36429                    self.generate_expression(expr)?;
36430                }
36431            } else {
36432                // Single namespace - check if DEFAULT
36433                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36434                    self.write_keyword("DEFAULT");
36435                    self.write_space();
36436                }
36437                self.generate_expression(namespaces)?;
36438            }
36439            self.write("), ");
36440        }
36441
36442        // XPath expression
36443        self.generate_expression(&e.this)?;
36444
36445        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36446        if let Some(passing) = &e.passing {
36447            self.write_space();
36448            self.write_keyword("PASSING");
36449            self.write_space();
36450            // Unwrap Tuple if present to avoid extra parentheses
36451            if let Expression::Tuple(tuple) = passing.as_ref() {
36452                for (i, expr) in tuple.expressions.iter().enumerate() {
36453                    if i > 0 {
36454                        self.write(", ");
36455                    }
36456                    self.generate_expression(expr)?;
36457                }
36458            } else {
36459                self.generate_expression(passing)?;
36460            }
36461        }
36462
36463        // RETURNING SEQUENCE BY REF
36464        if e.by_ref.is_some() {
36465            self.write_space();
36466            self.write_keyword("RETURNING SEQUENCE BY REF");
36467        }
36468
36469        // COLUMNS clause
36470        if !e.columns.is_empty() {
36471            self.write_space();
36472            self.write_keyword("COLUMNS");
36473            self.write_space();
36474            for (i, col) in e.columns.iter().enumerate() {
36475                if i > 0 {
36476                    self.write(", ");
36477                }
36478                self.generate_expression(col)?;
36479            }
36480        }
36481
36482        self.write(")");
36483        Ok(())
36484    }
36485
36486    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36487        // Python: return self.connector_sql(expression, "XOR", stack)
36488        // Handles: this XOR expression or expressions joined by XOR
36489        if let Some(this) = &e.this {
36490            self.generate_expression(this)?;
36491            if let Some(expression) = &e.expression {
36492                self.write_space();
36493                self.write_keyword("XOR");
36494                self.write_space();
36495                self.generate_expression(expression)?;
36496            }
36497        }
36498
36499        // Handle multiple expressions
36500        for (i, expr) in e.expressions.iter().enumerate() {
36501            if i > 0 || e.this.is_some() {
36502                self.write_space();
36503                self.write_keyword("XOR");
36504                self.write_space();
36505            }
36506            self.generate_expression(expr)?;
36507        }
36508        Ok(())
36509    }
36510
36511    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36512        // ZIPF(this, elementcount [, gen])
36513        self.write_keyword("ZIPF");
36514        self.write("(");
36515        self.generate_expression(&e.this)?;
36516        if let Some(elementcount) = &e.elementcount {
36517            self.write(", ");
36518            self.generate_expression(elementcount)?;
36519        }
36520        if let Some(gen) = &e.gen {
36521            self.write(", ");
36522            self.generate_expression(gen)?;
36523        }
36524        self.write(")");
36525        Ok(())
36526    }
36527}
36528
36529impl Default for Generator {
36530    fn default() -> Self {
36531        Self::new()
36532    }
36533}
36534
36535#[cfg(test)]
36536mod tests {
36537    use super::*;
36538    use crate::parser::Parser;
36539
36540    fn roundtrip(sql: &str) -> String {
36541        let ast = Parser::parse_sql(sql).unwrap();
36542        Generator::sql(&ast[0]).unwrap()
36543    }
36544
36545    #[test]
36546    fn test_simple_select() {
36547        let result = roundtrip("SELECT 1");
36548        assert_eq!(result, "SELECT 1");
36549    }
36550
36551    #[test]
36552    fn test_select_from() {
36553        let result = roundtrip("SELECT a, b FROM t");
36554        assert_eq!(result, "SELECT a, b FROM t");
36555    }
36556
36557    #[test]
36558    fn test_select_where() {
36559        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36560        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36561    }
36562
36563    #[test]
36564    fn test_select_join() {
36565        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36566        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36567    }
36568
36569    #[test]
36570    fn test_insert() {
36571        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36572        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36573    }
36574
36575    #[test]
36576    fn test_pretty_print() {
36577        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36578        let result = Generator::pretty_sql(&ast[0]).unwrap();
36579        assert!(result.contains('\n'));
36580    }
36581
36582    #[test]
36583    fn test_window_function() {
36584        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36585        assert_eq!(
36586            result,
36587            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36588        );
36589    }
36590
36591    #[test]
36592    fn test_window_function_with_frame() {
36593        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36594        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36595    }
36596
36597    #[test]
36598    fn test_aggregate_with_filter() {
36599        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36600        assert_eq!(
36601            result,
36602            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36603        );
36604    }
36605
36606    #[test]
36607    fn test_subscript() {
36608        let result = roundtrip("SELECT arr[0]");
36609        assert_eq!(result, "SELECT arr[0]");
36610    }
36611
36612    // DDL tests
36613    #[test]
36614    fn test_create_table() {
36615        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36616        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36617    }
36618
36619    #[test]
36620    fn test_create_table_with_constraints() {
36621        let result = roundtrip(
36622            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36623        );
36624        assert_eq!(
36625            result,
36626            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36627        );
36628    }
36629
36630    #[test]
36631    fn test_create_table_if_not_exists() {
36632        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36633        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36634    }
36635
36636    #[test]
36637    fn test_drop_table() {
36638        let result = roundtrip("DROP TABLE users");
36639        assert_eq!(result, "DROP TABLE users");
36640    }
36641
36642    #[test]
36643    fn test_drop_table_if_exists_cascade() {
36644        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36645        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36646    }
36647
36648    #[test]
36649    fn test_alter_table_add_column() {
36650        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36651        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36652    }
36653
36654    #[test]
36655    fn test_alter_table_drop_column() {
36656        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36657        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36658    }
36659
36660    #[test]
36661    fn test_create_index() {
36662        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36663        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36664    }
36665
36666    #[test]
36667    fn test_create_unique_index() {
36668        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36669        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36670    }
36671
36672    #[test]
36673    fn test_drop_index() {
36674        let result = roundtrip("DROP INDEX idx_name");
36675        assert_eq!(result, "DROP INDEX idx_name");
36676    }
36677
36678    #[test]
36679    fn test_create_view() {
36680        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36681        assert_eq!(
36682            result,
36683            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36684        );
36685    }
36686
36687    #[test]
36688    fn test_drop_view() {
36689        let result = roundtrip("DROP VIEW active_users");
36690        assert_eq!(result, "DROP VIEW active_users");
36691    }
36692
36693    #[test]
36694    fn test_truncate() {
36695        let result = roundtrip("TRUNCATE TABLE users");
36696        assert_eq!(result, "TRUNCATE TABLE users");
36697    }
36698
36699    #[test]
36700    fn test_string_literal_escaping_default() {
36701        // Default: double single quotes
36702        let result = roundtrip("SELECT 'hello'");
36703        assert_eq!(result, "SELECT 'hello'");
36704
36705        // Single quotes are doubled
36706        let result = roundtrip("SELECT 'it''s a test'");
36707        assert_eq!(result, "SELECT 'it''s a test'");
36708    }
36709
36710    #[test]
36711    fn test_not_in_style_prefix_default_generic() {
36712        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36713        assert_eq!(
36714            result,
36715            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36716        );
36717    }
36718
36719    #[test]
36720    fn test_not_in_style_infix_generic_override() {
36721        let ast =
36722            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36723                .unwrap();
36724        let config = GeneratorConfig {
36725            not_in_style: NotInStyle::Infix,
36726            ..Default::default()
36727        };
36728        let mut gen = Generator::with_config(config);
36729        let result = gen.generate(&ast[0]).unwrap();
36730        assert_eq!(
36731            result,
36732            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36733        );
36734    }
36735
36736    #[test]
36737    fn test_string_literal_escaping_mysql() {
36738        use crate::dialects::DialectType;
36739
36740        let config = GeneratorConfig {
36741            dialect: Some(DialectType::MySQL),
36742            ..Default::default()
36743        };
36744
36745        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36746        let mut gen = Generator::with_config(config.clone());
36747        let result = gen.generate(&ast[0]).unwrap();
36748        assert_eq!(result, "SELECT 'hello'");
36749
36750        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36751        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36752        let mut gen = Generator::with_config(config.clone());
36753        let result = gen.generate(&ast[0]).unwrap();
36754        assert_eq!(result, "SELECT 'it''s'");
36755    }
36756
36757    #[test]
36758    fn test_string_literal_escaping_postgres() {
36759        use crate::dialects::DialectType;
36760
36761        let config = GeneratorConfig {
36762            dialect: Some(DialectType::PostgreSQL),
36763            ..Default::default()
36764        };
36765
36766        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36767        let mut gen = Generator::with_config(config.clone());
36768        let result = gen.generate(&ast[0]).unwrap();
36769        assert_eq!(result, "SELECT 'hello'");
36770
36771        // PostgreSQL uses doubled quotes for regular strings
36772        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36773        let mut gen = Generator::with_config(config.clone());
36774        let result = gen.generate(&ast[0]).unwrap();
36775        assert_eq!(result, "SELECT 'it''s'");
36776    }
36777
36778    #[test]
36779    fn test_string_literal_escaping_bigquery() {
36780        use crate::dialects::DialectType;
36781
36782        let config = GeneratorConfig {
36783            dialect: Some(DialectType::BigQuery),
36784            ..Default::default()
36785        };
36786
36787        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36788        let mut gen = Generator::with_config(config.clone());
36789        let result = gen.generate(&ast[0]).unwrap();
36790        assert_eq!(result, "SELECT 'hello'");
36791
36792        // BigQuery escapes single quotes with backslash
36793        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36794        let mut gen = Generator::with_config(config.clone());
36795        let result = gen.generate(&ast[0]).unwrap();
36796        assert_eq!(result, "SELECT 'it\\'s'");
36797    }
36798
36799    #[test]
36800    fn test_generate_deep_and_chain_without_stack_growth() {
36801        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36802            Expression::column("c0"),
36803            Expression::number(0),
36804        )));
36805
36806        for i in 1..2500 {
36807            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36808                Expression::column(format!("c{i}")),
36809                Expression::number(i as i64),
36810            )));
36811            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
36812        }
36813
36814        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
36815        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36816    }
36817
36818    #[test]
36819    fn test_generate_deep_or_chain_without_stack_growth() {
36820        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36821            Expression::column("c0"),
36822            Expression::number(0),
36823        )));
36824
36825        for i in 1..2500 {
36826            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36827                Expression::column(format!("c{i}")),
36828                Expression::number(i as i64),
36829            )));
36830            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
36831        }
36832
36833        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
36834        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36835    }
36836}