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    unsupported_messages: Vec<String>,
45    indent_level: usize,
46    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
47    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
48    athena_hive_context: bool,
49    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
50    sqlite_inline_pk_columns: std::collections::HashSet<String>,
51    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
52    merge_strip_qualifiers: Vec<String>,
53    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
54    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
55    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
56    /// Negative values indicate map key context (should NOT be wrapped).
57    clickhouse_nullable_depth: i32,
58}
59
60/// Controls how SQL function names are cased in generated output.
61///
62/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
63/// - `Lower` -- `count`, `sum`, `coalesce`
64/// - `None` -- preserve the original casing from the parsed input
65#[derive(Debug, Clone, Copy, PartialEq, Default)]
66pub enum NormalizeFunctions {
67    /// Emit function names in UPPER CASE (default).
68    #[default]
69    Upper,
70    /// Emit function names in lower case.
71    Lower,
72    /// Preserve the original casing from the parsed input.
73    None,
74}
75
76/// Strategy for generating row-limiting clauses across SQL dialects.
77#[derive(Debug, Clone, Copy, PartialEq, Default)]
78pub enum LimitFetchStyle {
79    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
80    #[default]
81    Limit,
82    /// `TOP n` -- TSQL (SQL Server).
83    Top,
84    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
85    FetchFirst,
86}
87
88/// Strategy for rendering negated IN predicates.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
90pub enum NotInStyle {
91    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
92    #[default]
93    Prefix,
94    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
95    Infix,
96}
97
98/// Controls how the generator reacts when it encounters unsupported output.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum UnsupportedLevel {
101    /// Ignore unsupported diagnostics and continue generation.
102    Ignore,
103    /// Collect unsupported diagnostics and continue generation.
104    #[default]
105    Warn,
106    /// Collect unsupported diagnostics and raise after generation completes.
107    Raise,
108    /// Raise immediately when the first unsupported feature is encountered.
109    Immediate,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113enum ConnectorOperator {
114    And,
115    Or,
116}
117
118impl ConnectorOperator {
119    fn keyword(self) -> &'static str {
120        match self {
121            Self::And => "AND",
122            Self::Or => "OR",
123        }
124    }
125}
126
127/// Identifier quote style (start/end characters)
128#[derive(Debug, Clone, Copy, PartialEq)]
129pub struct IdentifierQuoteStyle {
130    /// Start character for quoting identifiers (e.g., '"', '`', '[')
131    pub start: char,
132    /// End character for quoting identifiers (e.g., '"', '`', ']')
133    pub end: char,
134}
135
136impl Default for IdentifierQuoteStyle {
137    fn default() -> Self {
138        Self {
139            start: '"',
140            end: '"',
141        }
142    }
143}
144
145impl IdentifierQuoteStyle {
146    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
147    pub const DOUBLE_QUOTE: Self = Self {
148        start: '"',
149        end: '"',
150    };
151    /// Backtick style (MySQL, BigQuery, Spark, Hive)
152    pub const BACKTICK: Self = Self {
153        start: '`',
154        end: '`',
155    };
156    /// Square bracket style (TSQL, SQLite)
157    pub const BRACKET: Self = Self {
158        start: '[',
159        end: ']',
160    };
161}
162
163/// Configuration for the SQL [`Generator`].
164///
165/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
166/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
167/// syntax, feature support flags, and more.
168///
169/// Most users should start from `GeneratorConfig::default()` and override only the
170/// fields they need. Dialect-specific presets are applied automatically when
171/// `dialect` is set via the higher-level transpilation API.
172///
173/// # Key fields
174///
175/// | Field | Default | Purpose |
176/// |-------|---------|---------|
177/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
178/// | `pretty` | `false` | Enable multi-line, indented output |
179/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
180/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
181/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
182/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
183/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
184#[derive(Debug, Clone)]
185pub struct GeneratorConfig {
186    // ===== Basic formatting =====
187    /// Pretty print with indentation
188    pub pretty: bool,
189    /// Indentation string (default 2 spaces)
190    pub indent: String,
191    /// Maximum text width before wrapping (default 80)
192    pub max_text_width: usize,
193    /// Quote identifier style (deprecated, use identifier_quote_style instead)
194    pub identifier_quote: char,
195    /// Identifier quote style with separate start/end characters
196    pub identifier_quote_style: IdentifierQuoteStyle,
197    /// Uppercase keywords
198    pub uppercase_keywords: bool,
199    /// Normalize identifiers to lowercase when generating
200    pub normalize_identifiers: bool,
201    /// Dialect type for dialect-specific generation
202    pub dialect: Option<crate::dialects::DialectType>,
203    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
204    pub source_dialect: Option<crate::dialects::DialectType>,
205    /// How unsupported generation should be handled.
206    pub unsupported_level: UnsupportedLevel,
207    /// Maximum number of unsupported diagnostics to include in raised errors.
208    pub max_unsupported: usize,
209    /// How to output function names (UPPER, lower, or as-is)
210    pub normalize_functions: NormalizeFunctions,
211    /// String escape character
212    pub string_escape: char,
213    /// Whether identifiers are case-sensitive
214    pub case_sensitive_identifiers: bool,
215    /// Whether unquoted identifiers can start with a digit
216    pub identifiers_can_start_with_digit: bool,
217    /// Whether to always quote identifiers regardless of reserved keyword status
218    /// Used by dialects like Athena/Presto that prefer quoted identifiers
219    pub always_quote_identifiers: bool,
220    /// How to render negated IN predicates in generic output.
221    pub not_in_style: NotInStyle,
222
223    // ===== Null handling =====
224    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
225    /// True: Full Support, false: No support
226    pub null_ordering_supported: bool,
227    /// Whether ignore nulls is inside the agg or outside
228    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
229    pub ignore_nulls_in_func: bool,
230    /// Whether the NVL2 function is supported
231    pub nvl2_supported: bool,
232
233    // ===== Limit/Fetch =====
234    /// How to output LIMIT clauses
235    pub limit_fetch_style: LimitFetchStyle,
236    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
237    pub limit_is_top: bool,
238    /// Whether limit and fetch allows expressions or just literals
239    pub limit_only_literals: bool,
240
241    // ===== Interval =====
242    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
243    pub single_string_interval: bool,
244    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
245    pub interval_allows_plural_form: bool,
246
247    // ===== CTE =====
248    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
249    pub cte_recursive_keyword_required: bool,
250
251    // ===== VALUES =====
252    /// Whether VALUES can be used as a table source
253    pub values_as_table: bool,
254    /// Wrap derived values in parens (standard but Spark doesn't support)
255    pub wrap_derived_values: bool,
256
257    // ===== TABLESAMPLE =====
258    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
259    pub tablesample_seed_keyword: &'static str,
260    /// Whether parentheses are required around the table sample's expression
261    pub tablesample_requires_parens: bool,
262    /// Whether a table sample clause's size needs to be followed by ROWS keyword
263    pub tablesample_size_is_rows: bool,
264    /// The keyword(s) to use when generating a sample clause
265    pub tablesample_keywords: &'static str,
266    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
267    pub tablesample_with_method: bool,
268    /// Whether the table alias comes after tablesample (Oracle, Hive)
269    pub alias_post_tablesample: bool,
270
271    // ===== Aggregate =====
272    /// Whether aggregate FILTER (WHERE ...) is supported
273    pub aggregate_filter_supported: bool,
274    /// Whether DISTINCT can be followed by multiple args in an AggFunc
275    pub multi_arg_distinct: bool,
276    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
277    pub quantified_no_paren_space: bool,
278    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
279    pub supports_median: bool,
280
281    // ===== SELECT =====
282    /// Whether SELECT ... INTO is supported
283    pub supports_select_into: bool,
284    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
285    pub locking_reads_supported: bool,
286
287    // ===== Table/Join =====
288    /// Whether a table is allowed to be renamed with a db
289    pub rename_table_with_db: bool,
290    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
291    pub semi_anti_join_with_side: bool,
292    /// Whether named columns are allowed in table aliases
293    pub supports_table_alias_columns: bool,
294    /// Whether join hints should be generated
295    pub join_hints: bool,
296    /// Whether table hints should be generated
297    pub table_hints: bool,
298    /// Whether query hints should be generated
299    pub query_hints: bool,
300    /// What kind of separator to use for query hints
301    pub query_hint_sep: &'static str,
302    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
303    pub supports_column_join_marks: bool,
304
305    // ===== DDL =====
306    /// Whether CREATE INDEX USING method should have no space before column parens
307    /// true: `USING btree(col)`, false: `USING btree (col)`
308    pub index_using_no_space: bool,
309    /// Whether UNLOGGED tables can be created
310    pub supports_unlogged_tables: bool,
311    /// Whether CREATE TABLE LIKE statement is supported
312    pub supports_create_table_like: bool,
313    /// Whether the LikeProperty needs to be inside the schema clause
314    pub like_property_inside_schema: bool,
315    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
316    pub alter_table_include_column_keyword: bool,
317    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
318    pub supports_table_copy: bool,
319    /// The syntax to use when altering the type of a column
320    pub alter_set_type: &'static str,
321    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
322    pub alter_set_wrapped: bool,
323
324    // ===== Timestamp/Timezone =====
325    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
326    pub tz_to_with_time_zone: bool,
327    /// Whether CONVERT_TIMEZONE() is supported
328    pub supports_convert_timezone: bool,
329
330    // ===== JSON =====
331    /// Whether the JSON extraction operators expect a value of type JSON
332    pub json_type_required_for_extraction: bool,
333    /// Whether bracketed keys like ["foo"] are supported in JSON paths
334    pub json_path_bracketed_key_supported: bool,
335    /// Whether to escape keys using single quotes in JSON paths
336    pub json_path_single_quote_escape: bool,
337    /// Whether to quote the generated expression of JsonPath
338    pub quote_json_path: bool,
339    /// What delimiter to use for separating JSON key/value pairs
340    pub json_key_value_pair_sep: &'static str,
341
342    // ===== COPY =====
343    /// Whether parameters from COPY statement are wrapped in parentheses
344    pub copy_params_are_wrapped: bool,
345    /// Whether values of params are set with "=" token or empty space
346    pub copy_params_eq_required: bool,
347    /// Whether COPY statement has INTO keyword
348    pub copy_has_into_keyword: bool,
349
350    // ===== Window functions =====
351    /// Whether EXCLUDE in window specification is supported
352    pub supports_window_exclude: bool,
353    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
354    pub unnest_with_ordinality: bool,
355    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
356    /// Exasol uses lowercase for these specific keywords
357    pub lowercase_window_frame_keywords: bool,
358    /// Whether to normalize single-bound window frames to BETWEEN form
359    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
360    pub normalize_window_frame_between: bool,
361
362    // ===== Array =====
363    /// Whether ARRAY_CONCAT can be generated with varlen args
364    pub array_concat_is_var_len: bool,
365    /// Whether exp.ArraySize should generate the dimension arg too
366    /// None -> Doesn't support, false -> optional, true -> required
367    pub array_size_dim_required: Option<bool>,
368    /// Whether any(f(x) for x in array) can be implemented
369    pub can_implement_array_any: bool,
370    /// Function used for array size
371    pub array_size_name: &'static str,
372
373    // ===== BETWEEN =====
374    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
375    pub supports_between_flags: bool,
376
377    // ===== Boolean =====
378    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
379    pub is_bool_allowed: bool,
380    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
381    pub ensure_bools: bool,
382
383    // ===== EXTRACT =====
384    /// Whether to generate an unquoted value for EXTRACT's date part argument
385    pub extract_allows_quotes: bool,
386    /// Whether to normalize date parts in EXTRACT
387    pub normalize_extract_date_parts: bool,
388
389    // ===== Other features =====
390    /// Whether the conditional TRY(expression) function is supported
391    pub try_supported: bool,
392    /// Whether the UESCAPE syntax in unicode strings is supported
393    pub supports_uescape: bool,
394    /// Whether the function TO_NUMBER is supported
395    pub supports_to_number: bool,
396    /// Whether CONCAT requires >1 arguments
397    pub supports_single_arg_concat: bool,
398    /// Whether LAST_DAY function supports a date part argument
399    pub last_day_supports_date_part: bool,
400    /// Whether a projection can explode into multiple rows
401    pub supports_exploding_projections: bool,
402    /// Whether UNIX_SECONDS(timestamp) is supported
403    pub supports_unix_seconds: bool,
404    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
405    pub supports_like_quantifiers: bool,
406    /// Whether multi-argument DECODE(...) function is supported
407    pub supports_decode_case: bool,
408    /// Whether set op modifiers apply to the outer set op or select
409    pub set_op_modifiers: bool,
410    /// Whether FROM is supported in UPDATE statements
411    pub update_statement_supports_from: bool,
412
413    // ===== COLLATE =====
414    /// Whether COLLATE is a function instead of a binary operator
415    pub collate_is_func: bool,
416
417    // ===== INSERT =====
418    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
419    pub duplicate_key_update_with_set: bool,
420    /// INSERT OVERWRITE TABLE x override
421    pub insert_overwrite: &'static str,
422
423    // ===== RETURNING =====
424    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
425    pub returning_end: bool,
426
427    // ===== MERGE =====
428    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
429    pub matched_by_source: bool,
430
431    // ===== CREATE FUNCTION =====
432    /// Whether create function uses an AS before the RETURN
433    pub create_function_return_as: bool,
434    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
435    pub parameter_default_equals: bool,
436
437    // ===== COMPUTED COLUMN =====
438    /// Whether to include the type of a computed column in the CREATE DDL
439    pub computed_column_with_type: bool,
440
441    // ===== UNPIVOT =====
442    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
443    pub unpivot_aliases_are_identifiers: bool,
444
445    // ===== STAR =====
446    /// The keyword to use when generating a star projection with excluded columns
447    pub star_except: &'static str,
448
449    // ===== HEX =====
450    /// The HEX function name
451    pub hex_func: &'static str,
452
453    // ===== WITH =====
454    /// The keywords to use when prefixing WITH based properties
455    pub with_properties_prefix: &'static str,
456
457    // ===== PAD =====
458    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
459    pub pad_fill_pattern_is_required: bool,
460
461    // ===== INDEX =====
462    /// The string used for creating an index on a table
463    pub index_on: &'static str,
464
465    // ===== GROUPING =====
466    /// The separator for grouping sets and rollups
467    pub groupings_sep: &'static str,
468
469    // ===== STRUCT =====
470    /// Delimiters for STRUCT type
471    pub struct_delimiter: (&'static str, &'static str),
472    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
473    pub struct_curly_brace_notation: bool,
474    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
475    pub array_bracket_only: bool,
476    /// Separator between struct field name and type (": " for Hive, " " for others)
477    pub struct_field_sep: &'static str,
478
479    // ===== EXCEPT/INTERSECT =====
480    /// Whether EXCEPT and INTERSECT operations can return duplicates
481    pub except_intersect_support_all_clause: bool,
482
483    // ===== PARAMETERS/PLACEHOLDERS =====
484    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
485    pub parameter_token: &'static str,
486    /// Named placeholder token (: for most, % for PostgreSQL)
487    pub named_placeholder_token: &'static str,
488
489    // ===== DATA TYPES =====
490    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
491    pub data_type_specifiers_allowed: bool,
492
493    // ===== COMMENT =====
494    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
495    /// StarRocks and Doris use naked COMMENT syntax without `=`
496    pub schema_comment_with_eq: bool,
497}
498
499impl Default for GeneratorConfig {
500    fn default() -> Self {
501        Self {
502            // ===== Basic formatting =====
503            pretty: false,
504            indent: "  ".to_string(),
505            max_text_width: 80,
506            identifier_quote: '"',
507            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
508            uppercase_keywords: true,
509            normalize_identifiers: false,
510            dialect: None,
511            source_dialect: None,
512            unsupported_level: UnsupportedLevel::Warn,
513            max_unsupported: 3,
514            normalize_functions: NormalizeFunctions::Upper,
515            string_escape: '\'',
516            case_sensitive_identifiers: false,
517            identifiers_can_start_with_digit: false,
518            always_quote_identifiers: false,
519            not_in_style: NotInStyle::Prefix,
520
521            // ===== Null handling =====
522            null_ordering_supported: true,
523            ignore_nulls_in_func: false,
524            nvl2_supported: true,
525
526            // ===== Limit/Fetch =====
527            limit_fetch_style: LimitFetchStyle::Limit,
528            limit_is_top: false,
529            limit_only_literals: false,
530
531            // ===== Interval =====
532            single_string_interval: false,
533            interval_allows_plural_form: true,
534
535            // ===== CTE =====
536            cte_recursive_keyword_required: true,
537
538            // ===== VALUES =====
539            values_as_table: true,
540            wrap_derived_values: true,
541
542            // ===== TABLESAMPLE =====
543            tablesample_seed_keyword: "SEED",
544            tablesample_requires_parens: true,
545            tablesample_size_is_rows: true,
546            tablesample_keywords: "TABLESAMPLE",
547            tablesample_with_method: true,
548            alias_post_tablesample: false,
549
550            // ===== Aggregate =====
551            aggregate_filter_supported: true,
552            multi_arg_distinct: true,
553            quantified_no_paren_space: false,
554            supports_median: true,
555
556            // ===== SELECT =====
557            supports_select_into: false,
558            locking_reads_supported: true,
559
560            // ===== Table/Join =====
561            rename_table_with_db: true,
562            semi_anti_join_with_side: true,
563            supports_table_alias_columns: true,
564            join_hints: true,
565            table_hints: true,
566            query_hints: true,
567            query_hint_sep: ", ",
568            supports_column_join_marks: false,
569
570            // ===== DDL =====
571            index_using_no_space: false,
572            supports_unlogged_tables: false,
573            supports_create_table_like: true,
574            like_property_inside_schema: false,
575            alter_table_include_column_keyword: true,
576            supports_table_copy: true,
577            alter_set_type: "SET DATA TYPE",
578            alter_set_wrapped: false,
579
580            // ===== Timestamp/Timezone =====
581            tz_to_with_time_zone: false,
582            supports_convert_timezone: false,
583
584            // ===== JSON =====
585            json_type_required_for_extraction: false,
586            json_path_bracketed_key_supported: true,
587            json_path_single_quote_escape: false,
588            quote_json_path: true,
589            json_key_value_pair_sep: ":",
590
591            // ===== COPY =====
592            copy_params_are_wrapped: true,
593            copy_params_eq_required: false,
594            copy_has_into_keyword: true,
595
596            // ===== Window functions =====
597            supports_window_exclude: false,
598            unnest_with_ordinality: true,
599            lowercase_window_frame_keywords: false,
600            normalize_window_frame_between: false,
601
602            // ===== Array =====
603            array_concat_is_var_len: true,
604            array_size_dim_required: None,
605            can_implement_array_any: false,
606            array_size_name: "ARRAY_LENGTH",
607
608            // ===== BETWEEN =====
609            supports_between_flags: false,
610
611            // ===== Boolean =====
612            is_bool_allowed: true,
613            ensure_bools: false,
614
615            // ===== EXTRACT =====
616            extract_allows_quotes: true,
617            normalize_extract_date_parts: false,
618
619            // ===== Other features =====
620            try_supported: true,
621            supports_uescape: true,
622            supports_to_number: true,
623            supports_single_arg_concat: true,
624            last_day_supports_date_part: true,
625            supports_exploding_projections: true,
626            supports_unix_seconds: false,
627            supports_like_quantifiers: true,
628            supports_decode_case: true,
629            set_op_modifiers: true,
630            update_statement_supports_from: true,
631
632            // ===== COLLATE =====
633            collate_is_func: false,
634
635            // ===== INSERT =====
636            duplicate_key_update_with_set: true,
637            insert_overwrite: " OVERWRITE TABLE",
638
639            // ===== RETURNING =====
640            returning_end: true,
641
642            // ===== MERGE =====
643            matched_by_source: true,
644
645            // ===== CREATE FUNCTION =====
646            create_function_return_as: true,
647            parameter_default_equals: false,
648
649            // ===== COMPUTED COLUMN =====
650            computed_column_with_type: true,
651
652            // ===== UNPIVOT =====
653            unpivot_aliases_are_identifiers: true,
654
655            // ===== STAR =====
656            star_except: "EXCEPT",
657
658            // ===== HEX =====
659            hex_func: "HEX",
660
661            // ===== WITH =====
662            with_properties_prefix: "WITH",
663
664            // ===== PAD =====
665            pad_fill_pattern_is_required: false,
666
667            // ===== INDEX =====
668            index_on: "ON",
669
670            // ===== GROUPING =====
671            groupings_sep: ",",
672
673            // ===== STRUCT =====
674            struct_delimiter: ("<", ">"),
675            struct_curly_brace_notation: false,
676            array_bracket_only: false,
677            struct_field_sep: " ",
678
679            // ===== EXCEPT/INTERSECT =====
680            except_intersect_support_all_clause: true,
681
682            // ===== PARAMETERS/PLACEHOLDERS =====
683            parameter_token: "@",
684            named_placeholder_token: ":",
685
686            // ===== DATA TYPES =====
687            data_type_specifiers_allowed: false,
688
689            // ===== COMMENT =====
690            schema_comment_with_eq: true,
691        }
692    }
693}
694
695/// SQL reserved keywords that require quoting when used as identifiers
696/// Based on ANSI SQL standards and common dialect-specific reserved words
697mod reserved_keywords {
698    use std::collections::HashSet;
699    use std::sync::LazyLock;
700
701    /// Standard SQL reserved keywords (ANSI SQL:2016)
702    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
703        [
704            "all",
705            "alter",
706            "and",
707            "any",
708            "array",
709            "as",
710            "asc",
711            "at",
712            "authorization",
713            "begin",
714            "between",
715            "both",
716            "by",
717            "case",
718            "cast",
719            "check",
720            "collate",
721            "column",
722            "commit",
723            "constraint",
724            "create",
725            "cross",
726            "cube",
727            "current",
728            "current_date",
729            "current_time",
730            "current_timestamp",
731            "current_user",
732            "default",
733            "delete",
734            "desc",
735            "distinct",
736            "drop",
737            "else",
738            "end",
739            "escape",
740            "except",
741            "execute",
742            "exists",
743            "external",
744            "false",
745            "fetch",
746            "filter",
747            "for",
748            "foreign",
749            "from",
750            "full",
751            "function",
752            "grant",
753            "group",
754            "grouping",
755            "having",
756            "if",
757            "in",
758            "index",
759            "inner",
760            "insert",
761            "intersect",
762            "interval",
763            "into",
764            "is",
765            "join",
766            "key",
767            "leading",
768            "left",
769            "like",
770            "limit",
771            "local",
772            "localtime",
773            "localtimestamp",
774            "match",
775            "merge",
776            "natural",
777            "no",
778            "not",
779            "null",
780            "of",
781            "offset",
782            "on",
783            "only",
784            "or",
785            "order",
786            "outer",
787            "over",
788            "partition",
789            "primary",
790            "procedure",
791            "range",
792            "references",
793            "right",
794            "rollback",
795            "rollup",
796            "row",
797            "rows",
798            "select",
799            "session_user",
800            "set",
801            "some",
802            "table",
803            "tablesample",
804            "then",
805            "to",
806            "trailing",
807            "true",
808            "truncate",
809            "union",
810            "unique",
811            "unknown",
812            "update",
813            "user",
814            "using",
815            "values",
816            "view",
817            "when",
818            "where",
819            "window",
820            "with",
821        ]
822        .into_iter()
823        .collect()
824    });
825
826    /// BigQuery-specific reserved keywords
827    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
828    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
829        let mut set = SQL_RESERVED.clone();
830        set.extend([
831            "assert_rows_modified",
832            "at",
833            "contains",
834            "cube",
835            "current",
836            "define",
837            "enum",
838            "escape",
839            "exclude",
840            "following",
841            "for",
842            "groups",
843            "hash",
844            "ignore",
845            "lateral",
846            "lookup",
847            "new",
848            "no",
849            "nulls",
850            "of",
851            "over",
852            "preceding",
853            "proto",
854            "qualify",
855            "recursive",
856            "respect",
857            "struct",
858            "tablesample",
859            "treat",
860            "unbounded",
861            "unnest",
862            "window",
863            "within",
864        ]);
865        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
866        set.remove("grant");
867        set.remove("key");
868        set.remove("index");
869        set.remove("values");
870        set.remove("table");
871        set
872    });
873
874    /// MySQL-specific reserved keywords
875    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
876        let mut set = SQL_RESERVED.clone();
877        set.extend([
878            "accessible",
879            "add",
880            "analyze",
881            "asensitive",
882            "before",
883            "bigint",
884            "binary",
885            "blob",
886            "call",
887            "cascade",
888            "change",
889            "char",
890            "character",
891            "condition",
892            "continue",
893            "convert",
894            "current_date",
895            "current_time",
896            "current_timestamp",
897            "current_user",
898            "cursor",
899            "database",
900            "databases",
901            "day_hour",
902            "day_microsecond",
903            "day_minute",
904            "day_second",
905            "dec",
906            "decimal",
907            "declare",
908            "delayed",
909            "describe",
910            "deterministic",
911            "distinctrow",
912            "div",
913            "double",
914            "dual",
915            "each",
916            "elseif",
917            "enclosed",
918            "escaped",
919            "exit",
920            "explain",
921            "float",
922            "float4",
923            "float8",
924            "force",
925            "get",
926            "high_priority",
927            "hour_microsecond",
928            "hour_minute",
929            "hour_second",
930            "ignore",
931            "infile",
932            "inout",
933            "insensitive",
934            "int",
935            "int1",
936            "int2",
937            "int3",
938            "int4",
939            "int8",
940            "integer",
941            "iterate",
942            "keys",
943            "kill",
944            "leave",
945            "linear",
946            "lines",
947            "load",
948            "lock",
949            "long",
950            "longblob",
951            "longtext",
952            "loop",
953            "low_priority",
954            "master_ssl_verify_server_cert",
955            "maxvalue",
956            "mediumblob",
957            "mediumint",
958            "mediumtext",
959            "middleint",
960            "minute_microsecond",
961            "minute_second",
962            "mod",
963            "modifies",
964            "no_write_to_binlog",
965            "numeric",
966            "optimize",
967            "option",
968            "optionally",
969            "out",
970            "outfile",
971            "precision",
972            "purge",
973            "read",
974            "reads",
975            "real",
976            "regexp",
977            "release",
978            "rename",
979            "repeat",
980            "replace",
981            "require",
982            "resignal",
983            "restrict",
984            "return",
985            "revoke",
986            "rlike",
987            "schema",
988            "schemas",
989            "second_microsecond",
990            "sensitive",
991            "separator",
992            "show",
993            "signal",
994            "smallint",
995            "spatial",
996            "specific",
997            "sql",
998            "sql_big_result",
999            "sql_calc_found_rows",
1000            "sql_small_result",
1001            "sqlexception",
1002            "sqlstate",
1003            "sqlwarning",
1004            "ssl",
1005            "starting",
1006            "straight_join",
1007            "terminated",
1008            "text",
1009            "tinyblob",
1010            "tinyint",
1011            "tinytext",
1012            "trigger",
1013            "undo",
1014            "unlock",
1015            "unsigned",
1016            "usage",
1017            "utc_date",
1018            "utc_time",
1019            "utc_timestamp",
1020            "varbinary",
1021            "varchar",
1022            "varcharacter",
1023            "varying",
1024            "while",
1025            "write",
1026            "xor",
1027            "year_month",
1028            "zerofill",
1029        ]);
1030        set.remove("table");
1031        set
1032    });
1033
1034    /// Doris-specific reserved keywords
1035    /// Extends MySQL reserved with additional Doris-specific words
1036    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1037        let mut set = MYSQL_RESERVED.clone();
1038        set.extend([
1039            "aggregate",
1040            "anti",
1041            "array",
1042            "backend",
1043            "backup",
1044            "begin",
1045            "bitmap",
1046            "boolean",
1047            "broker",
1048            "buckets",
1049            "cached",
1050            "cancel",
1051            "cast",
1052            "catalog",
1053            "charset",
1054            "cluster",
1055            "collation",
1056            "columns",
1057            "comment",
1058            "commit",
1059            "config",
1060            "connection",
1061            "count",
1062            "current",
1063            "data",
1064            "date",
1065            "datetime",
1066            "day",
1067            "deferred",
1068            "distributed",
1069            "dynamic",
1070            "enable",
1071            "end",
1072            "events",
1073            "export",
1074            "external",
1075            "fields",
1076            "first",
1077            "follower",
1078            "format",
1079            "free",
1080            "frontend",
1081            "full",
1082            "functions",
1083            "global",
1084            "grants",
1085            "hash",
1086            "help",
1087            "hour",
1088            "install",
1089            "intermediate",
1090            "json",
1091            "label",
1092            "last",
1093            "less",
1094            "level",
1095            "link",
1096            "local",
1097            "location",
1098            "max",
1099            "merge",
1100            "min",
1101            "minute",
1102            "modify",
1103            "month",
1104            "name",
1105            "names",
1106            "negative",
1107            "nulls",
1108            "observer",
1109            "offset",
1110            "only",
1111            "open",
1112            "overwrite",
1113            "password",
1114            "path",
1115            "plan",
1116            "plugin",
1117            "plugins",
1118            "policy",
1119            "process",
1120            "properties",
1121            "property",
1122            "query",
1123            "quota",
1124            "recover",
1125            "refresh",
1126            "repair",
1127            "replica",
1128            "repository",
1129            "resource",
1130            "restore",
1131            "resume",
1132            "role",
1133            "roles",
1134            "rollback",
1135            "rollup",
1136            "routine",
1137            "sample",
1138            "second",
1139            "semi",
1140            "session",
1141            "signed",
1142            "snapshot",
1143            "start",
1144            "stats",
1145            "status",
1146            "stop",
1147            "stream",
1148            "string",
1149            "sum",
1150            "tables",
1151            "tablet",
1152            "temporary",
1153            "text",
1154            "timestamp",
1155            "transaction",
1156            "trash",
1157            "trim",
1158            "truncate",
1159            "type",
1160            "user",
1161            "value",
1162            "variables",
1163            "verbose",
1164            "version",
1165            "view",
1166            "warnings",
1167            "week",
1168            "work",
1169            "year",
1170        ]);
1171        set
1172    });
1173
1174    /// PostgreSQL-specific reserved keywords
1175    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1176        let mut set = SQL_RESERVED.clone();
1177        set.extend([
1178            "analyse",
1179            "analyze",
1180            "asymmetric",
1181            "binary",
1182            "collation",
1183            "concurrently",
1184            "current_catalog",
1185            "current_role",
1186            "current_schema",
1187            "deferrable",
1188            "do",
1189            "freeze",
1190            "ilike",
1191            "initially",
1192            "isnull",
1193            "lateral",
1194            "notnull",
1195            "placing",
1196            "returning",
1197            "similar",
1198            "symmetric",
1199            "variadic",
1200            "verbose",
1201        ]);
1202        // PostgreSQL doesn't require quoting for these keywords
1203        set.remove("default");
1204        set.remove("interval");
1205        set.remove("match");
1206        set.remove("offset");
1207        set.remove("table");
1208        set
1209    });
1210
1211    /// Redshift-specific reserved keywords
1212    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1213    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1214    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1215        [
1216            "aes128",
1217            "aes256",
1218            "all",
1219            "allowoverwrite",
1220            "analyse",
1221            "analyze",
1222            "and",
1223            "any",
1224            "array",
1225            "as",
1226            "asc",
1227            "authorization",
1228            "az64",
1229            "backup",
1230            "between",
1231            "binary",
1232            "blanksasnull",
1233            "both",
1234            "bytedict",
1235            "bzip2",
1236            "case",
1237            "cast",
1238            "check",
1239            "collate",
1240            "column",
1241            "constraint",
1242            "create",
1243            "credentials",
1244            "cross",
1245            "current_date",
1246            "current_time",
1247            "current_timestamp",
1248            "current_user",
1249            "current_user_id",
1250            "default",
1251            "deferrable",
1252            "deflate",
1253            "defrag",
1254            "delta",
1255            "delta32k",
1256            "desc",
1257            "disable",
1258            "distinct",
1259            "do",
1260            "else",
1261            "emptyasnull",
1262            "enable",
1263            "encode",
1264            "encrypt",
1265            "encryption",
1266            "end",
1267            "except",
1268            "explicit",
1269            "false",
1270            "for",
1271            "foreign",
1272            "freeze",
1273            "from",
1274            "full",
1275            "globaldict256",
1276            "globaldict64k",
1277            "grant",
1278            "group",
1279            "gzip",
1280            "having",
1281            "identity",
1282            "ignore",
1283            "ilike",
1284            "in",
1285            "initially",
1286            "inner",
1287            "intersect",
1288            "interval",
1289            "into",
1290            "is",
1291            "isnull",
1292            "join",
1293            "leading",
1294            "left",
1295            "like",
1296            "limit",
1297            "localtime",
1298            "localtimestamp",
1299            "lun",
1300            "luns",
1301            "lzo",
1302            "lzop",
1303            "minus",
1304            "mostly16",
1305            "mostly32",
1306            "mostly8",
1307            "natural",
1308            "new",
1309            "not",
1310            "notnull",
1311            "null",
1312            "nulls",
1313            "off",
1314            "offline",
1315            "offset",
1316            "oid",
1317            "old",
1318            "on",
1319            "only",
1320            "open",
1321            "or",
1322            "order",
1323            "outer",
1324            "overlaps",
1325            "parallel",
1326            "partition",
1327            "percent",
1328            "permissions",
1329            "pivot",
1330            "placing",
1331            "primary",
1332            "raw",
1333            "readratio",
1334            "recover",
1335            "references",
1336            "rejectlog",
1337            "resort",
1338            "respect",
1339            "restore",
1340            "right",
1341            "select",
1342            "session_user",
1343            "similar",
1344            "snapshot",
1345            "some",
1346            "sysdate",
1347            "system",
1348            "table",
1349            "tag",
1350            "tdes",
1351            "text255",
1352            "text32k",
1353            "then",
1354            "timestamp",
1355            "to",
1356            "top",
1357            "trailing",
1358            "true",
1359            "truncatecolumns",
1360            "type",
1361            "union",
1362            "unique",
1363            "unnest",
1364            "unpivot",
1365            "user",
1366            "using",
1367            "verbose",
1368            "wallet",
1369            "when",
1370            "where",
1371            "with",
1372            "without",
1373        ]
1374        .into_iter()
1375        .collect()
1376    });
1377
1378    /// DuckDB-specific reserved keywords
1379    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1380        let mut set = POSTGRES_RESERVED.clone();
1381        set.extend([
1382            "anti",
1383            "asof",
1384            "columns",
1385            "describe",
1386            "groups",
1387            "macro",
1388            "pivot",
1389            "pivot_longer",
1390            "pivot_wider",
1391            "qualify",
1392            "replace",
1393            "respect",
1394            "semi",
1395            "show",
1396            "table",
1397            "unpivot",
1398        ]);
1399        set.remove("at");
1400        set.remove("key");
1401        set.remove("row");
1402        set
1403    });
1404
1405    /// Presto/Trino/Athena-specific reserved keywords
1406    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1407        let mut set = SQL_RESERVED.clone();
1408        set.extend([
1409            "alter",
1410            "and",
1411            "as",
1412            "between",
1413            "by",
1414            "case",
1415            "cast",
1416            "constraint",
1417            "create",
1418            "cross",
1419            "cube",
1420            "current_catalog",
1421            "current_date",
1422            "current_path",
1423            "current_role",
1424            "current_schema",
1425            "current_time",
1426            "current_timestamp",
1427            "current_user",
1428            "deallocate",
1429            "delete",
1430            "describe",
1431            "distinct",
1432            "drop",
1433            "else",
1434            "end",
1435            "escape",
1436            "except",
1437            "execute",
1438            "exists",
1439            "extract",
1440            "false",
1441            "for",
1442            "from",
1443            "full",
1444            "group",
1445            "grouping",
1446            "having",
1447            "in",
1448            "inner",
1449            "insert",
1450            "intersect",
1451            "into",
1452            "is",
1453            "join",
1454            "json_array",
1455            "json_exists",
1456            "json_object",
1457            "json_query",
1458            "json_table",
1459            "json_value",
1460            "left",
1461            "like",
1462            "listagg",
1463            "localtime",
1464            "localtimestamp",
1465            "natural",
1466            "normalize",
1467            "not",
1468            "null",
1469            "on",
1470            "or",
1471            "order",
1472            "outer",
1473            "prepare",
1474            "recursive",
1475            "right",
1476            "rollup",
1477            "select",
1478            "skip",
1479            "table",
1480            "then",
1481            "trim",
1482            "true",
1483            "uescape",
1484            "union",
1485            "unnest",
1486            "using",
1487            "values",
1488            "when",
1489            "where",
1490            "with",
1491        ]);
1492        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1493        set.remove("key");
1494        set
1495    });
1496
1497    /// StarRocks-specific reserved keywords
1498    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1499    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1500        [
1501            "add",
1502            "all",
1503            "alter",
1504            "analyze",
1505            "and",
1506            "array",
1507            "as",
1508            "asc",
1509            "between",
1510            "bigint",
1511            "bitmap",
1512            "both",
1513            "by",
1514            "case",
1515            "char",
1516            "character",
1517            "check",
1518            "collate",
1519            "column",
1520            "compaction",
1521            "convert",
1522            "create",
1523            "cross",
1524            "cube",
1525            "current_date",
1526            "current_role",
1527            "current_time",
1528            "current_timestamp",
1529            "current_user",
1530            "database",
1531            "databases",
1532            "decimal",
1533            "decimalv2",
1534            "decimal32",
1535            "decimal64",
1536            "decimal128",
1537            "default",
1538            "deferred",
1539            "delete",
1540            "dense_rank",
1541            "desc",
1542            "describe",
1543            "distinct",
1544            "double",
1545            "drop",
1546            "dual",
1547            "else",
1548            "except",
1549            "exists",
1550            "explain",
1551            "false",
1552            "first_value",
1553            "float",
1554            "for",
1555            "force",
1556            "from",
1557            "full",
1558            "function",
1559            "grant",
1560            "group",
1561            "grouping",
1562            "grouping_id",
1563            "groups",
1564            "having",
1565            "hll",
1566            "host",
1567            "if",
1568            "ignore",
1569            "immediate",
1570            "in",
1571            "index",
1572            "infile",
1573            "inner",
1574            "insert",
1575            "int",
1576            "integer",
1577            "intersect",
1578            "into",
1579            "is",
1580            "join",
1581            "json",
1582            "key",
1583            "keys",
1584            "kill",
1585            "lag",
1586            "largeint",
1587            "last_value",
1588            "lateral",
1589            "lead",
1590            "left",
1591            "like",
1592            "limit",
1593            "load",
1594            "localtime",
1595            "localtimestamp",
1596            "maxvalue",
1597            "minus",
1598            "mod",
1599            "not",
1600            "ntile",
1601            "null",
1602            "on",
1603            "or",
1604            "order",
1605            "outer",
1606            "outfile",
1607            "over",
1608            "partition",
1609            "percentile",
1610            "primary",
1611            "procedure",
1612            "qualify",
1613            "range",
1614            "rank",
1615            "read",
1616            "regexp",
1617            "release",
1618            "rename",
1619            "replace",
1620            "revoke",
1621            "right",
1622            "rlike",
1623            "row",
1624            "row_number",
1625            "rows",
1626            "schema",
1627            "schemas",
1628            "select",
1629            "set",
1630            "set_var",
1631            "show",
1632            "smallint",
1633            "system",
1634            "table",
1635            "terminated",
1636            "text",
1637            "then",
1638            "tinyint",
1639            "to",
1640            "true",
1641            "union",
1642            "unique",
1643            "unsigned",
1644            "update",
1645            "use",
1646            "using",
1647            "values",
1648            "varchar",
1649            "when",
1650            "where",
1651            "with",
1652        ]
1653        .into_iter()
1654        .collect()
1655    });
1656
1657    /// SingleStore-specific reserved keywords
1658    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1659    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1660        let mut set = MYSQL_RESERVED.clone();
1661        set.extend([
1662            // Additional SingleStore reserved keywords from Python sqlglot
1663            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1664            "abs",
1665            "account",
1666            "acos",
1667            "adddate",
1668            "addtime",
1669            "admin",
1670            "aes_decrypt",
1671            "aes_encrypt",
1672            "aggregate",
1673            "aggregates",
1674            "aggregator",
1675            "anti_join",
1676            "any_value",
1677            "approx_count_distinct",
1678            "approx_percentile",
1679            "arrange",
1680            "arrangement",
1681            "asin",
1682            "atan",
1683            "atan2",
1684            "attach",
1685            "autostats",
1686            "avro",
1687            "background",
1688            "backup",
1689            "batch",
1690            "batches",
1691            "boot_strapping",
1692            "ceil",
1693            "ceiling",
1694            "coercibility",
1695            "columnar",
1696            "columnstore",
1697            "compile",
1698            "concurrent",
1699            "connection_id",
1700            "cos",
1701            "cot",
1702            "current_security_groups",
1703            "current_security_roles",
1704            "dayname",
1705            "dayofmonth",
1706            "dayofweek",
1707            "dayofyear",
1708            "degrees",
1709            "dot_product",
1710            "dump",
1711            "durability",
1712            "earliest",
1713            "echo",
1714            "election",
1715            "euclidean_distance",
1716            "exp",
1717            "extractor",
1718            "extractors",
1719            "floor",
1720            "foreground",
1721            "found_rows",
1722            "from_base64",
1723            "from_days",
1724            "from_unixtime",
1725            "fs",
1726            "fulltext",
1727            "gc",
1728            "gcs",
1729            "geography",
1730            "geography_area",
1731            "geography_contains",
1732            "geography_distance",
1733            "geography_intersects",
1734            "geography_latitude",
1735            "geography_length",
1736            "geography_longitude",
1737            "geographypoint",
1738            "geography_point",
1739            "geography_within_distance",
1740            "geometry",
1741            "geometry_area",
1742            "geometry_contains",
1743            "geometry_distance",
1744            "geometry_filter",
1745            "geometry_intersects",
1746            "geometry_length",
1747            "geometrypoint",
1748            "geometry_point",
1749            "geometry_within_distance",
1750            "geometry_x",
1751            "geometry_y",
1752            "greatest",
1753            "groups",
1754            "group_concat",
1755            "gzip",
1756            "hdfs",
1757            "hex",
1758            "highlight",
1759            "ifnull",
1760            "ilike",
1761            "inet_aton",
1762            "inet_ntoa",
1763            "inet6_aton",
1764            "inet6_ntoa",
1765            "initcap",
1766            "instr",
1767            "interpreter_mode",
1768            "isnull",
1769            "json",
1770            "json_agg",
1771            "json_array_contains_double",
1772            "json_array_contains_json",
1773            "json_array_contains_string",
1774            "json_delete_key",
1775            "json_extract_double",
1776            "json_extract_json",
1777            "json_extract_string",
1778            "json_extract_bigint",
1779            "json_get_type",
1780            "json_length",
1781            "json_set_double",
1782            "json_set_json",
1783            "json_set_string",
1784            "kafka",
1785            "lag",
1786            "last_day",
1787            "last_insert_id",
1788            "latest",
1789            "lcase",
1790            "lead",
1791            "leaf",
1792            "least",
1793            "leaves",
1794            "length",
1795            "license",
1796            "links",
1797            "llvm",
1798            "ln",
1799            "load",
1800            "locate",
1801            "log",
1802            "log10",
1803            "log2",
1804            "lpad",
1805            "lz4",
1806            "management",
1807            "match",
1808            "mbc",
1809            "md5",
1810            "median",
1811            "memsql",
1812            "memsql_deserialize",
1813            "memsql_serialize",
1814            "metadata",
1815            "microsecond",
1816            "minute",
1817            "model",
1818            "monthname",
1819            "months_between",
1820            "mpl",
1821            "namespace",
1822            "node",
1823            "noparam",
1824            "now",
1825            "nth_value",
1826            "ntile",
1827            "nullcols",
1828            "nullif",
1829            "object",
1830            "octet_length",
1831            "offsets",
1832            "online",
1833            "optimizer",
1834            "orphan",
1835            "parquet",
1836            "partitions",
1837            "pause",
1838            "percentile_cont",
1839            "percentile_disc",
1840            "periodic",
1841            "persisted",
1842            "pi",
1843            "pipeline",
1844            "pipelines",
1845            "plancache",
1846            "plugins",
1847            "pool",
1848            "pools",
1849            "pow",
1850            "power",
1851            "process",
1852            "processlist",
1853            "profile",
1854            "profiles",
1855            "quarter",
1856            "queries",
1857            "query",
1858            "radians",
1859            "rand",
1860            "record",
1861            "reduce",
1862            "redundancy",
1863            "regexp_match",
1864            "regexp_substr",
1865            "remote",
1866            "replication",
1867            "resource",
1868            "resource_pool",
1869            "restore",
1870            "retry",
1871            "role",
1872            "roles",
1873            "round",
1874            "rpad",
1875            "rtrim",
1876            "running",
1877            "s3",
1878            "scalar",
1879            "sec_to_time",
1880            "second",
1881            "security_lists_intersect",
1882            "semi_join",
1883            "sha",
1884            "sha1",
1885            "sha2",
1886            "shard",
1887            "sharded",
1888            "sharded_id",
1889            "sigmoid",
1890            "sign",
1891            "sin",
1892            "skip",
1893            "sleep",
1894            "snapshot",
1895            "soname",
1896            "sparse",
1897            "spatial_check_index",
1898            "split",
1899            "sqrt",
1900            "standalone",
1901            "std",
1902            "stddev",
1903            "stddev_pop",
1904            "stddev_samp",
1905            "stop",
1906            "str_to_date",
1907            "subdate",
1908            "substr",
1909            "substring_index",
1910            "success",
1911            "synchronize",
1912            "table_checksum",
1913            "tan",
1914            "task",
1915            "timediff",
1916            "time_bucket",
1917            "time_format",
1918            "time_to_sec",
1919            "timestampadd",
1920            "timestampdiff",
1921            "to_base64",
1922            "to_char",
1923            "to_date",
1924            "to_days",
1925            "to_json",
1926            "to_number",
1927            "to_seconds",
1928            "to_timestamp",
1929            "tracelogs",
1930            "transform",
1931            "trim",
1932            "trunc",
1933            "truncate",
1934            "ucase",
1935            "unhex",
1936            "unix_timestamp",
1937            "utc_date",
1938            "utc_time",
1939            "utc_timestamp",
1940            "vacuum",
1941            "variance",
1942            "var_pop",
1943            "var_samp",
1944            "vector_sub",
1945            "voting",
1946            "week",
1947            "weekday",
1948            "weekofyear",
1949            "workload",
1950            "year",
1951        ]);
1952        // Remove "all" because ORDER BY ALL needs ALL unquoted
1953        set.remove("all");
1954        set
1955    });
1956
1957    /// SQLite-specific reserved keywords
1958    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1959    /// Reference: https://www.sqlite.org/lang_keywords.html
1960    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1961        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1962        [
1963            "abort",
1964            "action",
1965            "add",
1966            "after",
1967            "all",
1968            "alter",
1969            "always",
1970            "analyze",
1971            "and",
1972            "as",
1973            "asc",
1974            "attach",
1975            "autoincrement",
1976            "before",
1977            "begin",
1978            "between",
1979            "by",
1980            "cascade",
1981            "case",
1982            "cast",
1983            "check",
1984            "collate",
1985            "column",
1986            "commit",
1987            "conflict",
1988            "constraint",
1989            "create",
1990            "cross",
1991            "current",
1992            "current_date",
1993            "current_time",
1994            "current_timestamp",
1995            "database",
1996            "default",
1997            "deferrable",
1998            "deferred",
1999            "delete",
2000            "desc",
2001            "detach",
2002            "distinct",
2003            "do",
2004            "drop",
2005            "each",
2006            "else",
2007            "end",
2008            "escape",
2009            "except",
2010            "exclude",
2011            "exclusive",
2012            "exists",
2013            "explain",
2014            "fail",
2015            "filter",
2016            "first",
2017            "following",
2018            "for",
2019            "foreign",
2020            "from",
2021            "full",
2022            "generated",
2023            "glob",
2024            "group",
2025            "groups",
2026            "having",
2027            "if",
2028            "ignore",
2029            "immediate",
2030            "in",
2031            "index",
2032            "indexed",
2033            "initially",
2034            "inner",
2035            "insert",
2036            "instead",
2037            "intersect",
2038            "into",
2039            "is",
2040            "isnull",
2041            "join",
2042            "key",
2043            "last",
2044            "left",
2045            "like",
2046            "limit",
2047            "natural",
2048            "no",
2049            "not",
2050            "nothing",
2051            "notnull",
2052            "null",
2053            "nulls",
2054            "of",
2055            "offset",
2056            "on",
2057            "or",
2058            "order",
2059            "others",
2060            "outer",
2061            "partition",
2062            "plan",
2063            "pragma",
2064            "preceding",
2065            "primary",
2066            "query",
2067            "raise",
2068            "range",
2069            "recursive",
2070            "references",
2071            "regexp",
2072            "reindex",
2073            "release",
2074            "rename",
2075            "replace",
2076            "restrict",
2077            "returning",
2078            "right",
2079            "rollback",
2080            "row",
2081            "rows",
2082            "savepoint",
2083            "select",
2084            "set",
2085            "table",
2086            "temp",
2087            "temporary",
2088            "then",
2089            "ties",
2090            "to",
2091            "transaction",
2092            "trigger",
2093            "unbounded",
2094            "union",
2095            "unique",
2096            "update",
2097            "using",
2098            "vacuum",
2099            "values",
2100            "view",
2101            "virtual",
2102            "when",
2103            "where",
2104            "window",
2105            "with",
2106            "without",
2107        ]
2108        .into_iter()
2109        .collect()
2110    });
2111}
2112
2113impl Generator {
2114    /// Create a new generator with the default configuration.
2115    ///
2116    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2117    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2118    /// and no dialect-specific transformations.
2119    pub fn new() -> Self {
2120        Self::with_config(GeneratorConfig::default())
2121    }
2122
2123    /// Create a generator with a custom [`GeneratorConfig`].
2124    ///
2125    /// Use this when you need dialect-specific output, pretty-printing, or other
2126    /// non-default settings.
2127    pub fn with_config(config: GeneratorConfig) -> Self {
2128        Self {
2129            config,
2130            output: String::new(),
2131            unsupported_messages: Vec::new(),
2132            indent_level: 0,
2133            athena_hive_context: false,
2134            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2135            merge_strip_qualifiers: Vec::new(),
2136            clickhouse_nullable_depth: 0,
2137        }
2138    }
2139
2140    /// Add column aliases to a query expression for TSQL SELECT INTO.
2141    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2142    /// Recursively processes all SELECT expressions in the query tree.
2143    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2144        match expr {
2145            Expression::Select(mut select) => {
2146                // Add aliases to all select expressions that don't already have them
2147                select.expressions = select
2148                    .expressions
2149                    .into_iter()
2150                    .map(|e| Self::add_alias_to_expression(e))
2151                    .collect();
2152
2153                // Recursively process subqueries in FROM clause
2154                if let Some(ref mut from) = select.from {
2155                    from.expressions = from
2156                        .expressions
2157                        .iter()
2158                        .cloned()
2159                        .map(|e| Self::add_column_aliases_to_query(e))
2160                        .collect();
2161                }
2162
2163                Expression::Select(select)
2164            }
2165            Expression::Subquery(mut sq) => {
2166                sq.this = Self::add_column_aliases_to_query(sq.this);
2167                Expression::Subquery(sq)
2168            }
2169            Expression::Paren(mut p) => {
2170                p.this = Self::add_column_aliases_to_query(p.this);
2171                Expression::Paren(p)
2172            }
2173            // For other expressions (Union, Intersect, etc.), pass through
2174            other => other,
2175        }
2176    }
2177
2178    /// Add an alias to a single select expression if it doesn't already have one.
2179    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2180    fn add_alias_to_expression(expr: Expression) -> Expression {
2181        use crate::expressions::Alias;
2182
2183        match &expr {
2184            // Already aliased - just return it
2185            Expression::Alias(_) => expr,
2186
2187            // Column reference: add alias from column name
2188            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2189                this: expr.clone(),
2190                alias: col.name.clone(),
2191                column_aliases: Vec::new(),
2192                pre_alias_comments: Vec::new(),
2193                trailing_comments: Vec::new(),
2194                inferred_type: None,
2195            })),
2196
2197            // Identifier: add alias from identifier name
2198            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2199                this: expr.clone(),
2200                alias: ident.clone(),
2201                column_aliases: Vec::new(),
2202                pre_alias_comments: Vec::new(),
2203                trailing_comments: Vec::new(),
2204                inferred_type: None,
2205            })),
2206
2207            // Subquery: recursively process and add alias if inner returns a named column
2208            Expression::Subquery(sq) => {
2209                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2210                // Subqueries that are already aliased keep their alias
2211                if sq.alias.is_some() {
2212                    processed
2213                } else {
2214                    // If there's no alias, keep it as-is (let TSQL handle it)
2215                    processed
2216                }
2217            }
2218
2219            // Star expressions (*) - don't alias
2220            Expression::Star(_) => expr,
2221
2222            // For other expressions, don't add an alias
2223            // (function calls, literals, etc. would need explicit aliases anyway)
2224            _ => expr,
2225        }
2226    }
2227
2228    /// Try to evaluate a constant arithmetic expression to a number literal.
2229    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2230    /// otherwise returns the original expression.
2231    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2232        match expr {
2233            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
2234            Expression::Add(op) => {
2235                let left = Self::try_evaluate_constant(&op.left)?;
2236                let right = Self::try_evaluate_constant(&op.right)?;
2237                Some(left + right)
2238            }
2239            Expression::Sub(op) => {
2240                let left = Self::try_evaluate_constant(&op.left)?;
2241                let right = Self::try_evaluate_constant(&op.right)?;
2242                Some(left - right)
2243            }
2244            Expression::Mul(op) => {
2245                let left = Self::try_evaluate_constant(&op.left)?;
2246                let right = Self::try_evaluate_constant(&op.right)?;
2247                Some(left * right)
2248            }
2249            Expression::Div(op) => {
2250                let left = Self::try_evaluate_constant(&op.left)?;
2251                let right = Self::try_evaluate_constant(&op.right)?;
2252                if right != 0 {
2253                    Some(left / right)
2254                } else {
2255                    None
2256                }
2257            }
2258            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2259            _ => None,
2260        }
2261    }
2262
2263    /// Check if an identifier is a reserved keyword for the current dialect
2264    fn is_reserved_keyword(&self, name: &str) -> bool {
2265        use crate::dialects::DialectType;
2266        let lower = name.to_lowercase();
2267        let lower_ref = lower.as_str();
2268
2269        match self.config.dialect {
2270            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2271            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2272                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2273            }
2274            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2275            Some(DialectType::SingleStore) => {
2276                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2277            }
2278            Some(DialectType::StarRocks) => {
2279                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2280            }
2281            Some(DialectType::PostgreSQL)
2282            | Some(DialectType::CockroachDB)
2283            | Some(DialectType::Materialize)
2284            | Some(DialectType::RisingWave) => {
2285                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2286            }
2287            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2288            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2289            // meaning it never quotes identifiers based on reserved word status.
2290            Some(DialectType::Snowflake) => false,
2291            // ClickHouse: don't quote reserved keywords to preserve identity output
2292            Some(DialectType::ClickHouse) => false,
2293            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2294            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2295            Some(DialectType::Teradata) => false,
2296            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2297            Some(DialectType::TSQL)
2298            | Some(DialectType::Fabric)
2299            | Some(DialectType::Oracle)
2300            | Some(DialectType::Spark)
2301            | Some(DialectType::Databricks)
2302            | Some(DialectType::Hive)
2303            | Some(DialectType::Solr) => false,
2304            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2305                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2306            }
2307            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2308            // For Generic dialect or None, don't add extra quoting to preserve identity
2309            Some(DialectType::Generic) | None => false,
2310            // For other dialects, use standard SQL reserved keywords
2311            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2312        }
2313    }
2314
2315    /// Normalize function name based on dialect settings
2316    fn normalize_func_name(&self, name: &str) -> String {
2317        match self.config.normalize_functions {
2318            NormalizeFunctions::Upper => name.to_uppercase(),
2319            NormalizeFunctions::Lower => name.to_lowercase(),
2320            NormalizeFunctions::None => name.to_string(),
2321        }
2322    }
2323
2324    /// Generate a SQL string from an AST expression.
2325    ///
2326    /// This is the primary generation method. It clears any previous internal state,
2327    /// walks the expression tree, and returns the resulting SQL text. The output
2328    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2329    ///
2330    /// The generator can be reused across multiple calls; each call to `generate`
2331    /// resets the internal buffer.
2332    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2333        self.output.clear();
2334        self.unsupported_messages.clear();
2335        self.generate_expression(expr)?;
2336        if self.config.unsupported_level == UnsupportedLevel::Raise
2337            && !self.unsupported_messages.is_empty()
2338        {
2339            return Err(crate::error::Error::generate(
2340                self.format_unsupported_messages(),
2341            ));
2342        }
2343        Ok(std::mem::take(&mut self.output))
2344    }
2345
2346    /// Returns the unsupported diagnostics collected during the most recent generate call.
2347    pub fn unsupported_messages(&self) -> &[String] {
2348        &self.unsupported_messages
2349    }
2350
2351    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2352        let message = message.into();
2353        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2354            return Err(crate::error::Error::generate(message));
2355        }
2356        self.unsupported_messages.push(message);
2357        Ok(())
2358    }
2359
2360    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2361        self.unsupported(message.to_string())?;
2362        self.write("/* ");
2363        self.write(message);
2364        self.write(" */");
2365        Ok(())
2366    }
2367
2368    fn format_unsupported_messages(&self) -> String {
2369        let limit = self.config.max_unsupported.max(1);
2370        if self.unsupported_messages.len() <= limit {
2371            return self.unsupported_messages.join("; ");
2372        }
2373
2374        let mut messages = self
2375            .unsupported_messages
2376            .iter()
2377            .take(limit)
2378            .cloned()
2379            .collect::<Vec<_>>();
2380        messages.push(format!(
2381            "... and {} more",
2382            self.unsupported_messages.len() - limit
2383        ));
2384        messages.join("; ")
2385    }
2386
2387    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2388    ///
2389    /// This is a static helper that creates a throwaway `Generator` internally.
2390    /// For repeated generation, prefer constructing a `Generator` once and calling
2391    /// [`generate`](Self::generate) on it.
2392    pub fn sql(expr: &Expression) -> Result<String> {
2393        let mut gen = Generator::new();
2394        gen.generate(expr)
2395    }
2396
2397    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2398    ///
2399    /// Produces human-readable output with newlines and indentation. A trailing
2400    /// semicolon is appended automatically if not already present.
2401    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2402        let config = GeneratorConfig {
2403            pretty: true,
2404            ..Default::default()
2405        };
2406        let mut gen = Generator::with_config(config);
2407        let mut sql = gen.generate(expr)?;
2408        // Add semicolon for pretty output
2409        if !sql.ends_with(';') {
2410            sql.push(';');
2411        }
2412        Ok(sql)
2413    }
2414
2415    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2416        match expr {
2417            Expression::Select(select) => self.generate_select(select),
2418            Expression::Union(union) => self.generate_union(union),
2419            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2420            Expression::Except(except) => self.generate_except(except),
2421            Expression::Insert(insert) => self.generate_insert(insert),
2422            Expression::Update(update) => self.generate_update(update),
2423            Expression::Delete(delete) => self.generate_delete(delete),
2424            Expression::Literal(lit) => self.generate_literal(lit),
2425            Expression::Boolean(b) => self.generate_boolean(b),
2426            Expression::Null(_) => {
2427                self.write_keyword("NULL");
2428                Ok(())
2429            }
2430            Expression::Identifier(id) => self.generate_identifier(id),
2431            Expression::Column(col) => self.generate_column(col),
2432            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2433            Expression::Connect(c) => self.generate_connect_expr(c),
2434            Expression::Prior(p) => self.generate_prior(p),
2435            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2436            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2437            Expression::Table(table) => self.generate_table(table),
2438            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2439            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2440            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2441            Expression::Star(star) => self.generate_star(star),
2442            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2443            Expression::Alias(alias) => self.generate_alias(alias),
2444            Expression::Cast(cast) => self.generate_cast(cast),
2445            Expression::Collation(coll) => self.generate_collation(coll),
2446            Expression::Case(case) => self.generate_case(case),
2447            Expression::Function(func) => self.generate_function(func),
2448            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2449            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2450            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2451            Expression::Interval(interval) => self.generate_interval(interval),
2452
2453            // String functions
2454            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2455            Expression::Substring(f) => self.generate_substring(f),
2456            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2457            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2458            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2459            Expression::Trim(f) => self.generate_trim(f),
2460            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2461            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2462            Expression::Replace(f) => self.generate_replace(f),
2463            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2464            Expression::Left(f) => self.generate_left_right("LEFT", f),
2465            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2466            Expression::Repeat(f) => self.generate_repeat(f),
2467            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2468            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2469            Expression::Split(f) => self.generate_split(f),
2470            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2471            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2472            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2473            Expression::Overlay(f) => self.generate_overlay(f),
2474
2475            // Math functions
2476            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2477            Expression::Round(f) => self.generate_round(f),
2478            Expression::Floor(f) => self.generate_floor(f),
2479            Expression::Ceil(f) => self.generate_ceil(f),
2480            Expression::Power(f) => self.generate_power(f),
2481            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2482            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2483            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2484            Expression::Log(f) => self.generate_log(f),
2485            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2486            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2487            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2488            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2489
2490            // Date/time functions
2491            Expression::CurrentDate(_) => {
2492                self.write_keyword("CURRENT_DATE");
2493                Ok(())
2494            }
2495            Expression::CurrentTime(f) => self.generate_current_time(f),
2496            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2497            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2498            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2499            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2500            Expression::DateDiff(f) => self.generate_datediff(f),
2501            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2502            Expression::Extract(f) => self.generate_extract(f),
2503            Expression::ToDate(f) => self.generate_to_date(f),
2504            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2505
2506            // Control flow functions
2507            Expression::Coalesce(f) => {
2508                // Use original function name if preserved (COALESCE, IFNULL)
2509                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2510                self.generate_vararg_func(func_name, &f.expressions)
2511            }
2512            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2513            Expression::IfFunc(f) => self.generate_if_func(f),
2514            Expression::IfNull(f) => self.generate_ifnull(f),
2515            Expression::Nvl(f) => self.generate_nvl(f),
2516            Expression::Nvl2(f) => self.generate_nvl2(f),
2517
2518            // Type conversion
2519            Expression::TryCast(cast) => self.generate_try_cast(cast),
2520            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2521
2522            // Typed aggregate functions
2523            Expression::Count(f) => self.generate_count(f),
2524            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2525            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2526            Expression::Min(f) => self.generate_agg_func("MIN", f),
2527            Expression::Max(f) => self.generate_agg_func("MAX", f),
2528            Expression::GroupConcat(f) => self.generate_group_concat(f),
2529            Expression::StringAgg(f) => self.generate_string_agg(f),
2530            Expression::ListAgg(f) => self.generate_listagg(f),
2531            Expression::ArrayAgg(f) => {
2532                // Allow cross-dialect transforms to override the function name
2533                // (e.g., COLLECT_LIST for Spark)
2534                let override_name = f
2535                    .name
2536                    .as_ref()
2537                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
2538                    .map(|n| n.to_uppercase());
2539                match override_name {
2540                    Some(name) => self.generate_agg_func(&name, f),
2541                    None => self.generate_agg_func("ARRAY_AGG", f),
2542                }
2543            }
2544            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2545            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2546            Expression::SumIf(f) => self.generate_sum_if(f),
2547            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2548            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2549            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2550            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2551            Expression::VarPop(f) => {
2552                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2553                    "VARIANCE_POP"
2554                } else {
2555                    "VAR_POP"
2556                };
2557                self.generate_agg_func(name, f)
2558            }
2559            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2560            Expression::Skewness(f) => {
2561                let name = match self.config.dialect {
2562                    Some(DialectType::Snowflake) => "SKEW",
2563                    _ => "SKEWNESS",
2564                };
2565                self.generate_agg_func(name, f)
2566            }
2567            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2568            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2569            Expression::First(f) => self.generate_agg_func("FIRST", f),
2570            Expression::Last(f) => self.generate_agg_func("LAST", f),
2571            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2572            Expression::ApproxDistinct(f) => {
2573                match self.config.dialect {
2574                    Some(DialectType::Hive)
2575                    | Some(DialectType::Spark)
2576                    | Some(DialectType::Databricks)
2577                    | Some(DialectType::BigQuery) => {
2578                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2579                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2580                    }
2581                    Some(DialectType::Redshift) => {
2582                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2583                        self.write_keyword("APPROXIMATE COUNT");
2584                        self.write("(");
2585                        self.write_keyword("DISTINCT");
2586                        self.write(" ");
2587                        self.generate_expression(&f.this)?;
2588                        self.write(")");
2589                        Ok(())
2590                    }
2591                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2592                }
2593            }
2594            Expression::ApproxCountDistinct(f) => {
2595                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2596            }
2597            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2598            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2599            Expression::LogicalAnd(f) => {
2600                let name = match self.config.dialect {
2601                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2602                    Some(DialectType::Spark)
2603                    | Some(DialectType::Databricks)
2604                    | Some(DialectType::PostgreSQL)
2605                    | Some(DialectType::DuckDB)
2606                    | Some(DialectType::Redshift) => "BOOL_AND",
2607                    Some(DialectType::Oracle)
2608                    | Some(DialectType::SQLite)
2609                    | Some(DialectType::MySQL) => "MIN",
2610                    _ => "BOOL_AND",
2611                };
2612                self.generate_agg_func(name, f)
2613            }
2614            Expression::LogicalOr(f) => {
2615                let name = match self.config.dialect {
2616                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2617                    Some(DialectType::Spark)
2618                    | Some(DialectType::Databricks)
2619                    | Some(DialectType::PostgreSQL)
2620                    | Some(DialectType::DuckDB)
2621                    | Some(DialectType::Redshift) => "BOOL_OR",
2622                    Some(DialectType::Oracle)
2623                    | Some(DialectType::SQLite)
2624                    | Some(DialectType::MySQL) => "MAX",
2625                    _ => "BOOL_OR",
2626                };
2627                self.generate_agg_func(name, f)
2628            }
2629
2630            // Typed window functions
2631            Expression::RowNumber(_) => {
2632                if self.config.dialect == Some(DialectType::ClickHouse) {
2633                    self.write("row_number");
2634                } else {
2635                    self.write_keyword("ROW_NUMBER");
2636                }
2637                self.write("()");
2638                Ok(())
2639            }
2640            Expression::Rank(r) => {
2641                self.write_keyword("RANK");
2642                self.write("(");
2643                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2644                if !r.args.is_empty() {
2645                    for (i, arg) in r.args.iter().enumerate() {
2646                        if i > 0 {
2647                            self.write(", ");
2648                        }
2649                        self.generate_expression(arg)?;
2650                    }
2651                } else if let Some(order_by) = &r.order_by {
2652                    // DuckDB: RANK(ORDER BY col)
2653                    self.write_keyword(" ORDER BY ");
2654                    for (i, ob) in order_by.iter().enumerate() {
2655                        if i > 0 {
2656                            self.write(", ");
2657                        }
2658                        self.generate_ordered(ob)?;
2659                    }
2660                }
2661                self.write(")");
2662                Ok(())
2663            }
2664            Expression::DenseRank(dr) => {
2665                self.write_keyword("DENSE_RANK");
2666                self.write("(");
2667                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2668                for (i, arg) in dr.args.iter().enumerate() {
2669                    if i > 0 {
2670                        self.write(", ");
2671                    }
2672                    self.generate_expression(arg)?;
2673                }
2674                self.write(")");
2675                Ok(())
2676            }
2677            Expression::NTile(f) => self.generate_ntile(f),
2678            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2679            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2680            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
2681            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
2682            Expression::NthValue(f) => self.generate_nth_value(f),
2683            Expression::PercentRank(pr) => {
2684                self.write_keyword("PERCENT_RANK");
2685                self.write("(");
2686                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2687                if !pr.args.is_empty() {
2688                    for (i, arg) in pr.args.iter().enumerate() {
2689                        if i > 0 {
2690                            self.write(", ");
2691                        }
2692                        self.generate_expression(arg)?;
2693                    }
2694                } else if let Some(order_by) = &pr.order_by {
2695                    // DuckDB: PERCENT_RANK(ORDER BY col)
2696                    self.write_keyword(" ORDER BY ");
2697                    for (i, ob) in order_by.iter().enumerate() {
2698                        if i > 0 {
2699                            self.write(", ");
2700                        }
2701                        self.generate_ordered(ob)?;
2702                    }
2703                }
2704                self.write(")");
2705                Ok(())
2706            }
2707            Expression::CumeDist(cd) => {
2708                self.write_keyword("CUME_DIST");
2709                self.write("(");
2710                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2711                if !cd.args.is_empty() {
2712                    for (i, arg) in cd.args.iter().enumerate() {
2713                        if i > 0 {
2714                            self.write(", ");
2715                        }
2716                        self.generate_expression(arg)?;
2717                    }
2718                } else if let Some(order_by) = &cd.order_by {
2719                    // DuckDB: CUME_DIST(ORDER BY col)
2720                    self.write_keyword(" ORDER BY ");
2721                    for (i, ob) in order_by.iter().enumerate() {
2722                        if i > 0 {
2723                            self.write(", ");
2724                        }
2725                        self.generate_ordered(ob)?;
2726                    }
2727                }
2728                self.write(")");
2729                Ok(())
2730            }
2731            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2732            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2733
2734            // Additional string functions
2735            Expression::Contains(f) => {
2736                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2737            }
2738            Expression::StartsWith(f) => {
2739                let name = match self.config.dialect {
2740                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2741                    _ => "STARTS_WITH",
2742                };
2743                self.generate_binary_func(name, &f.this, &f.expression)
2744            }
2745            Expression::EndsWith(f) => {
2746                let name = match self.config.dialect {
2747                    Some(DialectType::Snowflake) => "ENDSWITH",
2748                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2749                    Some(DialectType::ClickHouse) => "endsWith",
2750                    _ => "ENDS_WITH",
2751                };
2752                self.generate_binary_func(name, &f.this, &f.expression)
2753            }
2754            Expression::Position(f) => self.generate_position(f),
2755            Expression::Initcap(f) => match self.config.dialect {
2756                Some(DialectType::Presto)
2757                | Some(DialectType::Trino)
2758                | Some(DialectType::Athena) => {
2759                    self.write_keyword("REGEXP_REPLACE");
2760                    self.write("(");
2761                    self.generate_expression(&f.this)?;
2762                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2763                    Ok(())
2764                }
2765                _ => self.generate_simple_func("INITCAP", &f.this),
2766            },
2767            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2768            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2769            Expression::CharFunc(f) => self.generate_char_func(f),
2770            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2771            Expression::Levenshtein(f) => {
2772                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2773            }
2774
2775            // Additional math functions
2776            Expression::ModFunc(f) => self.generate_mod_func(f),
2777            Expression::Random(_) => {
2778                self.write_keyword("RANDOM");
2779                self.write("()");
2780                Ok(())
2781            }
2782            Expression::Rand(f) => self.generate_rand(f),
2783            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2784            Expression::Pi(_) => {
2785                self.write_keyword("PI");
2786                self.write("()");
2787                Ok(())
2788            }
2789            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2790            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2791            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2792            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2793            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2794            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2795            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2796            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2797            Expression::Atan2(f) => {
2798                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2799                self.generate_binary_func(name, &f.this, &f.expression)
2800            }
2801
2802            // Control flow
2803            Expression::Decode(f) => self.generate_decode(f),
2804
2805            // Additional date/time functions
2806            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2807            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2808            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2809            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2810            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2811            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2812            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2813            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2814            Expression::DayOfWeek(f) => {
2815                let name = match self.config.dialect {
2816                    Some(DialectType::Presto)
2817                    | Some(DialectType::Trino)
2818                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2819                    Some(DialectType::DuckDB) => "ISODOW",
2820                    _ => "DAYOFWEEK",
2821                };
2822                self.generate_simple_func(name, &f.this)
2823            }
2824            Expression::DayOfMonth(f) => {
2825                let name = match self.config.dialect {
2826                    Some(DialectType::Presto)
2827                    | Some(DialectType::Trino)
2828                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2829                    _ => "DAYOFMONTH",
2830                };
2831                self.generate_simple_func(name, &f.this)
2832            }
2833            Expression::DayOfYear(f) => {
2834                let name = match self.config.dialect {
2835                    Some(DialectType::Presto)
2836                    | Some(DialectType::Trino)
2837                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2838                    _ => "DAYOFYEAR",
2839                };
2840                self.generate_simple_func(name, &f.this)
2841            }
2842            Expression::WeekOfYear(f) => {
2843                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2844                let name = match self.config.dialect {
2845                    Some(DialectType::Hive)
2846                    | Some(DialectType::DuckDB)
2847                    | Some(DialectType::Spark)
2848                    | Some(DialectType::Databricks)
2849                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2850                    _ => "WEEK_OF_YEAR",
2851                };
2852                self.generate_simple_func(name, &f.this)
2853            }
2854            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2855            Expression::AddMonths(f) => {
2856                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2857            }
2858            Expression::MonthsBetween(f) => {
2859                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2860            }
2861            Expression::LastDay(f) => self.generate_last_day(f),
2862            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2863            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2864            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2865            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2866            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2867            Expression::MakeDate(f) => self.generate_make_date(f),
2868            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2869            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2870
2871            // Array functions
2872            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2873            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2874            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2875            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2876            Expression::ArrayContains(f) => {
2877                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2878            }
2879            Expression::ArrayPosition(f) => {
2880                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2881            }
2882            Expression::ArrayAppend(f) => {
2883                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2884            }
2885            Expression::ArrayPrepend(f) => {
2886                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2887            }
2888            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2889            Expression::ArraySort(f) => self.generate_array_sort(f),
2890            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2891            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2892            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2893            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2894            Expression::Unnest(f) => self.generate_unnest(f),
2895            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2896            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2897            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2898            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2899            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2900            Expression::ArrayCompact(f) => {
2901                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2902                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2903                    self.write("LIST_FILTER(");
2904                    self.generate_expression(&f.this)?;
2905                    self.write(", _u -> NOT _u IS NULL)");
2906                    Ok(())
2907                } else {
2908                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2909                }
2910            }
2911            Expression::ArrayIntersect(f) => {
2912                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2913                self.generate_vararg_func(func_name, &f.expressions)
2914            }
2915            Expression::ArrayUnion(f) => {
2916                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2917            }
2918            Expression::ArrayExcept(f) => {
2919                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2920            }
2921            Expression::ArrayRemove(f) => {
2922                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2923            }
2924            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2925            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2926            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2927
2928            // Struct functions
2929            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2930            Expression::StructExtract(f) => self.generate_struct_extract(f),
2931            Expression::NamedStruct(f) => self.generate_named_struct(f),
2932
2933            // Map functions
2934            Expression::MapFunc(f) => self.generate_map_constructor(f),
2935            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2936            Expression::MapFromArrays(f) => {
2937                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2938            }
2939            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2940            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2941            Expression::MapContainsKey(f) => {
2942                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2943            }
2944            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2945            Expression::ElementAt(f) => {
2946                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2947            }
2948            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2949            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2950
2951            // JSON functions
2952            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2953            Expression::JsonExtractScalar(f) => {
2954                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2955            }
2956            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2957            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2958            Expression::JsonObject(f) => self.generate_json_object(f),
2959            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2960            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2961            Expression::JsonArrayLength(f) => {
2962                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2963            }
2964            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2965            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2966            Expression::ParseJson(f) => {
2967                let name = match self.config.dialect {
2968                    Some(DialectType::Presto)
2969                    | Some(DialectType::Trino)
2970                    | Some(DialectType::Athena) => "JSON_PARSE",
2971                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2972                        // PostgreSQL: CAST(x AS JSON)
2973                        self.write_keyword("CAST");
2974                        self.write("(");
2975                        self.generate_expression(&f.this)?;
2976                        self.write_keyword(" AS ");
2977                        self.write_keyword("JSON");
2978                        self.write(")");
2979                        return Ok(());
2980                    }
2981                    Some(DialectType::Hive)
2982                    | Some(DialectType::Spark)
2983                    | Some(DialectType::MySQL)
2984                    | Some(DialectType::SingleStore)
2985                    | Some(DialectType::TiDB)
2986                    | Some(DialectType::TSQL) => {
2987                        // Hive/Spark/MySQL/TSQL: just emit the string literal
2988                        self.generate_expression(&f.this)?;
2989                        return Ok(());
2990                    }
2991                    Some(DialectType::DuckDB) => "JSON",
2992                    _ => "PARSE_JSON",
2993                };
2994                self.generate_simple_func(name, &f.this)
2995            }
2996            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
2997            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
2998            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
2999            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3000            Expression::JsonMergePatch(f) => {
3001                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3002            }
3003            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3004            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3005
3006            // Type casting/conversion
3007            Expression::Convert(f) => self.generate_convert(f),
3008            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3009
3010            // Additional expressions
3011            Expression::Lambda(f) => self.generate_lambda(f),
3012            Expression::Parameter(f) => self.generate_parameter(f),
3013            Expression::Placeholder(f) => self.generate_placeholder(f),
3014            Expression::NamedArgument(f) => self.generate_named_argument(f),
3015            Expression::TableArgument(f) => self.generate_table_argument(f),
3016            Expression::SqlComment(f) => self.generate_sql_comment(f),
3017
3018            // Additional predicates
3019            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3020            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3021            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3022            Expression::SimilarTo(f) => self.generate_similar_to(f),
3023            Expression::Any(f) => self.generate_quantified("ANY", f),
3024            Expression::All(f) => self.generate_quantified("ALL", f),
3025            Expression::Overlaps(f) => self.generate_overlaps(f),
3026
3027            // Bitwise operations
3028            Expression::BitwiseLeftShift(op) => {
3029                if matches!(
3030                    self.config.dialect,
3031                    Some(DialectType::Presto) | Some(DialectType::Trino)
3032                ) {
3033                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3034                    self.write("(");
3035                    self.generate_expression(&op.left)?;
3036                    self.write(", ");
3037                    self.generate_expression(&op.right)?;
3038                    self.write(")");
3039                    Ok(())
3040                } else if matches!(
3041                    self.config.dialect,
3042                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3043                ) {
3044                    self.write_keyword("SHIFTLEFT");
3045                    self.write("(");
3046                    self.generate_expression(&op.left)?;
3047                    self.write(", ");
3048                    self.generate_expression(&op.right)?;
3049                    self.write(")");
3050                    Ok(())
3051                } else {
3052                    self.generate_binary_op(op, "<<")
3053                }
3054            }
3055            Expression::BitwiseRightShift(op) => {
3056                if matches!(
3057                    self.config.dialect,
3058                    Some(DialectType::Presto) | Some(DialectType::Trino)
3059                ) {
3060                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3061                    self.write("(");
3062                    self.generate_expression(&op.left)?;
3063                    self.write(", ");
3064                    self.generate_expression(&op.right)?;
3065                    self.write(")");
3066                    Ok(())
3067                } else if matches!(
3068                    self.config.dialect,
3069                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3070                ) {
3071                    self.write_keyword("SHIFTRIGHT");
3072                    self.write("(");
3073                    self.generate_expression(&op.left)?;
3074                    self.write(", ");
3075                    self.generate_expression(&op.right)?;
3076                    self.write(")");
3077                    Ok(())
3078                } else {
3079                    self.generate_binary_op(op, ">>")
3080                }
3081            }
3082            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3083            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3084            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3085
3086            // Array/struct/map access
3087            Expression::Subscript(s) => self.generate_subscript(s),
3088            Expression::Dot(d) => self.generate_dot_access(d),
3089            Expression::MethodCall(m) => self.generate_method_call(m),
3090            Expression::ArraySlice(s) => self.generate_array_slice(s),
3091
3092            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3093            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3094            Expression::Add(op) => self.generate_binary_op(op, "+"),
3095            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3096            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3097            Expression::Div(op) => self.generate_binary_op(op, "/"),
3098            Expression::IntDiv(f) => {
3099                use crate::dialects::DialectType;
3100                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3101                    // DuckDB uses // operator for integer division
3102                    self.generate_expression(&f.this)?;
3103                    self.write(" // ");
3104                    self.generate_expression(&f.expression)?;
3105                    Ok(())
3106                } else if matches!(
3107                    self.config.dialect,
3108                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3109                ) {
3110                    // Hive/Spark use DIV as an infix operator
3111                    self.generate_expression(&f.this)?;
3112                    self.write(" ");
3113                    self.write_keyword("DIV");
3114                    self.write(" ");
3115                    self.generate_expression(&f.expression)?;
3116                    Ok(())
3117                } else {
3118                    // Other dialects use DIV function
3119                    self.write_keyword("DIV");
3120                    self.write("(");
3121                    self.generate_expression(&f.this)?;
3122                    self.write(", ");
3123                    self.generate_expression(&f.expression)?;
3124                    self.write(")");
3125                    Ok(())
3126                }
3127            }
3128            Expression::Mod(op) => {
3129                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3130                    self.generate_binary_op(op, "MOD")
3131                } else {
3132                    self.generate_binary_op(op, "%")
3133                }
3134            }
3135            Expression::Eq(op) => self.generate_binary_op(op, "="),
3136            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3137            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3138            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3139            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3140            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3141            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3142            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3143            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3144            Expression::Concat(op) => {
3145                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3146                if self.config.dialect == Some(DialectType::Solr) {
3147                    self.generate_binary_op(op, "OR")
3148                } else if self.config.dialect == Some(DialectType::MySQL) {
3149                    self.generate_mysql_concat_from_concat(op)
3150                } else {
3151                    self.generate_binary_op(op, "||")
3152                }
3153            }
3154            Expression::BitwiseAnd(op) => {
3155                // Presto/Trino use BITWISE_AND function
3156                if matches!(
3157                    self.config.dialect,
3158                    Some(DialectType::Presto) | Some(DialectType::Trino)
3159                ) {
3160                    self.write_keyword("BITWISE_AND");
3161                    self.write("(");
3162                    self.generate_expression(&op.left)?;
3163                    self.write(", ");
3164                    self.generate_expression(&op.right)?;
3165                    self.write(")");
3166                    Ok(())
3167                } else {
3168                    self.generate_binary_op(op, "&")
3169                }
3170            }
3171            Expression::BitwiseOr(op) => {
3172                // Presto/Trino use BITWISE_OR function
3173                if matches!(
3174                    self.config.dialect,
3175                    Some(DialectType::Presto) | Some(DialectType::Trino)
3176                ) {
3177                    self.write_keyword("BITWISE_OR");
3178                    self.write("(");
3179                    self.generate_expression(&op.left)?;
3180                    self.write(", ");
3181                    self.generate_expression(&op.right)?;
3182                    self.write(")");
3183                    Ok(())
3184                } else {
3185                    self.generate_binary_op(op, "|")
3186                }
3187            }
3188            Expression::BitwiseXor(op) => {
3189                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3190                if matches!(
3191                    self.config.dialect,
3192                    Some(DialectType::Presto) | Some(DialectType::Trino)
3193                ) {
3194                    self.write_keyword("BITWISE_XOR");
3195                    self.write("(");
3196                    self.generate_expression(&op.left)?;
3197                    self.write(", ");
3198                    self.generate_expression(&op.right)?;
3199                    self.write(")");
3200                    Ok(())
3201                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3202                    self.generate_binary_op(op, "#")
3203                } else {
3204                    self.generate_binary_op(op, "^")
3205                }
3206            }
3207            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3208            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3209            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3210            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3211            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3212            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3213            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3214            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3215            Expression::JSONBContains(f) => {
3216                // PostgreSQL JSONB contains key operator: a ? b
3217                self.generate_expression(&f.this)?;
3218                self.write_space();
3219                self.write("?");
3220                self.write_space();
3221                self.generate_expression(&f.expression)
3222            }
3223            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3224            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3225            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3226            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3227            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3228            Expression::BitwiseNot(op) => {
3229                // Presto/Trino use BITWISE_NOT function
3230                if matches!(
3231                    self.config.dialect,
3232                    Some(DialectType::Presto) | Some(DialectType::Trino)
3233                ) {
3234                    self.write_keyword("BITWISE_NOT");
3235                    self.write("(");
3236                    self.generate_expression(&op.this)?;
3237                    self.write(")");
3238                    Ok(())
3239                } else {
3240                    self.generate_unary_op(op, "~")
3241                }
3242            }
3243            Expression::In(in_expr) => self.generate_in(in_expr),
3244            Expression::Between(between) => self.generate_between(between),
3245            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3246            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3247            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3248            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3249            Expression::Is(is_expr) => self.generate_is(is_expr),
3250            Expression::Exists(exists) => self.generate_exists(exists),
3251            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3252            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3253            Expression::Paren(paren) => {
3254                // JoinedTable already outputs its own parentheses, so don't double-wrap
3255                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3256
3257                if !skip_parens {
3258                    self.write("(");
3259                    if self.config.pretty {
3260                        self.write_newline();
3261                        self.indent_level += 1;
3262                        self.write_indent();
3263                    }
3264                }
3265                self.generate_expression(&paren.this)?;
3266                if !skip_parens {
3267                    if self.config.pretty {
3268                        self.write_newline();
3269                        self.indent_level -= 1;
3270                        self.write_indent();
3271                    }
3272                    self.write(")");
3273                }
3274                // Output trailing comments after closing paren
3275                for comment in &paren.trailing_comments {
3276                    self.write(" ");
3277                    self.write_formatted_comment(comment);
3278                }
3279                Ok(())
3280            }
3281            Expression::Array(arr) => self.generate_array(arr),
3282            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3283            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3284            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3285            Expression::DataType(dt) => self.generate_data_type(dt),
3286            Expression::Raw(raw) => {
3287                self.write(&raw.sql);
3288                Ok(())
3289            }
3290            Expression::Command(cmd) => {
3291                self.write(&cmd.this);
3292                Ok(())
3293            }
3294            Expression::Kill(kill) => {
3295                self.write_keyword("KILL");
3296                if let Some(kind) = &kill.kind {
3297                    self.write_space();
3298                    self.write_keyword(kind);
3299                }
3300                self.write_space();
3301                self.generate_expression(&kill.this)?;
3302                Ok(())
3303            }
3304            Expression::Execute(exec) => {
3305                self.write_keyword("EXEC");
3306                self.write_space();
3307                self.generate_expression(&exec.this)?;
3308                for (i, param) in exec.parameters.iter().enumerate() {
3309                    if i == 0 {
3310                        self.write_space();
3311                    } else {
3312                        self.write(", ");
3313                    }
3314                    self.write(&param.name);
3315                    self.write("=");
3316                    self.generate_expression(&param.value)?;
3317                }
3318                Ok(())
3319            }
3320            Expression::Annotated(annotated) => {
3321                self.generate_expression(&annotated.this)?;
3322                for comment in &annotated.trailing_comments {
3323                    self.write(" ");
3324                    self.write_formatted_comment(comment);
3325                }
3326                Ok(())
3327            }
3328
3329            // DDL statements
3330            Expression::CreateTable(ct) => self.generate_create_table(ct),
3331            Expression::DropTable(dt) => self.generate_drop_table(dt),
3332            Expression::AlterTable(at) => self.generate_alter_table(at),
3333            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3334            Expression::DropIndex(di) => self.generate_drop_index(di),
3335            Expression::CreateView(cv) => self.generate_create_view(cv),
3336            Expression::DropView(dv) => self.generate_drop_view(dv),
3337            Expression::AlterView(av) => self.generate_alter_view(av),
3338            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3339            Expression::Truncate(tr) => self.generate_truncate(tr),
3340            Expression::Use(u) => self.generate_use(u),
3341            // Phase 4: Additional DDL statements
3342            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3343            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3344            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3345            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3346            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3347            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3348            Expression::DropFunction(df) => self.generate_drop_function(df),
3349            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3350            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3351            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3352            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3353            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3354            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3355            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3356            Expression::CreateType(ct) => self.generate_create_type(ct),
3357            Expression::DropType(dt) => self.generate_drop_type(dt),
3358            Expression::Describe(d) => self.generate_describe(d),
3359            Expression::Show(s) => self.generate_show(s),
3360
3361            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3362            Expression::Cache(c) => self.generate_cache(c),
3363            Expression::Uncache(u) => self.generate_uncache(u),
3364            Expression::LoadData(l) => self.generate_load_data(l),
3365            Expression::Pragma(p) => self.generate_pragma(p),
3366            Expression::Grant(g) => self.generate_grant(g),
3367            Expression::Revoke(r) => self.generate_revoke(r),
3368            Expression::Comment(c) => self.generate_comment(c),
3369            Expression::SetStatement(s) => self.generate_set_statement(s),
3370
3371            // PIVOT/UNPIVOT
3372            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3373            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3374
3375            // VALUES table constructor
3376            Expression::Values(values) => self.generate_values(values),
3377
3378            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3379            Expression::AIAgg(e) => self.generate_ai_agg(e),
3380            Expression::AIClassify(e) => self.generate_ai_classify(e),
3381            Expression::AddPartition(e) => self.generate_add_partition(e),
3382            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3383            Expression::Aliases(e) => self.generate_aliases(e),
3384            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3385            Expression::AlterColumn(e) => self.generate_alter_column(e),
3386            Expression::AlterSession(e) => self.generate_alter_session(e),
3387            Expression::AlterSet(e) => self.generate_alter_set(e),
3388            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3389            Expression::Analyze(e) => self.generate_analyze(e),
3390            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3391            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3392            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3393            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3394            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3395            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3396            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3397            Expression::Anonymous(e) => self.generate_anonymous(e),
3398            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3399            Expression::Apply(e) => self.generate_apply(e),
3400            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3401            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3402            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3403            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3404            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3405            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3406            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3407            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3408            Expression::ArgMax(e) => self.generate_arg_max(e),
3409            Expression::ArgMin(e) => self.generate_arg_min(e),
3410            Expression::ArrayAll(e) => self.generate_array_all(e),
3411            Expression::ArrayAny(e) => self.generate_array_any(e),
3412            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3413            Expression::ArraySum(e) => self.generate_array_sum(e),
3414            Expression::AtIndex(e) => self.generate_at_index(e),
3415            Expression::Attach(e) => self.generate_attach(e),
3416            Expression::AttachOption(e) => self.generate_attach_option(e),
3417            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3418            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3419            Expression::BackupProperty(e) => self.generate_backup_property(e),
3420            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3421            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3422            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3423            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3424            Expression::Booland(e) => self.generate_booland(e),
3425            Expression::Boolor(e) => self.generate_boolor(e),
3426            Expression::BuildProperty(e) => self.generate_build_property(e),
3427            Expression::ByteString(e) => self.generate_byte_string(e),
3428            Expression::CaseSpecificColumnConstraint(e) => {
3429                self.generate_case_specific_column_constraint(e)
3430            }
3431            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3432            Expression::Changes(e) => self.generate_changes(e),
3433            Expression::CharacterSetColumnConstraint(e) => {
3434                self.generate_character_set_column_constraint(e)
3435            }
3436            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3437            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3438            Expression::CheckJson(e) => self.generate_check_json(e),
3439            Expression::CheckXml(e) => self.generate_check_xml(e),
3440            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3441            Expression::Clone(e) => self.generate_clone(e),
3442            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3443            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3444            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3445            Expression::CollateProperty(e) => self.generate_collate_property(e),
3446            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3447            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3448            Expression::ColumnPosition(e) => self.generate_column_position(e),
3449            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3450            Expression::Columns(e) => self.generate_columns(e),
3451            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3452            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3453            Expression::Commit(e) => self.generate_commit(e),
3454            Expression::Comprehension(e) => self.generate_comprehension(e),
3455            Expression::Compress(e) => self.generate_compress(e),
3456            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3457            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3458            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3459            Expression::Constraint(e) => self.generate_constraint(e),
3460            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3461            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3462            Expression::Copy(e) => self.generate_copy(e),
3463            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3464            Expression::Corr(e) => self.generate_corr(e),
3465            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3466            Expression::CovarPop(e) => self.generate_covar_pop(e),
3467            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3468            Expression::Credentials(e) => self.generate_credentials(e),
3469            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3470            Expression::Cte(e) => self.generate_cte(e),
3471            Expression::Cube(e) => self.generate_cube(e),
3472            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3473            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3474            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3475            Expression::CurrentUser(e) => self.generate_current_user(e),
3476            Expression::DPipe(e) => self.generate_d_pipe(e),
3477            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3478            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3479            Expression::Date(e) => self.generate_date_func(e),
3480            Expression::DateBin(e) => self.generate_date_bin(e),
3481            Expression::DateFormatColumnConstraint(e) => {
3482                self.generate_date_format_column_constraint(e)
3483            }
3484            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3485            Expression::Datetime(e) => self.generate_datetime(e),
3486            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3487            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3488            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3489            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3490            Expression::Dayname(e) => self.generate_dayname(e),
3491            Expression::Declare(e) => self.generate_declare(e),
3492            Expression::DeclareItem(e) => self.generate_declare_item(e),
3493            Expression::DecodeCase(e) => self.generate_decode_case(e),
3494            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3495            Expression::DecompressString(e) => self.generate_decompress_string(e),
3496            Expression::Decrypt(e) => self.generate_decrypt(e),
3497            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3498            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3499            Expression::Detach(e) => self.generate_detach(e),
3500            Expression::DictProperty(e) => self.generate_dict_property(e),
3501            Expression::DictRange(e) => self.generate_dict_range(e),
3502            Expression::Directory(e) => self.generate_directory(e),
3503            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3504            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3505            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3506            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3507            Expression::DotProduct(e) => self.generate_dot_product(e),
3508            Expression::DropPartition(e) => self.generate_drop_partition(e),
3509            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3510            Expression::Elt(e) => self.generate_elt(e),
3511            Expression::Encode(e) => self.generate_encode(e),
3512            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3513            Expression::Encrypt(e) => self.generate_encrypt(e),
3514            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3515            Expression::EngineProperty(e) => self.generate_engine_property(e),
3516            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3517            Expression::EphemeralColumnConstraint(e) => {
3518                self.generate_ephemeral_column_constraint(e)
3519            }
3520            Expression::EqualNull(e) => self.generate_equal_null(e),
3521            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3522            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3523            Expression::Export(e) => self.generate_export(e),
3524            Expression::ExternalProperty(e) => self.generate_external_property(e),
3525            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3526            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3527            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3528            Expression::Fetch(e) => self.generate_fetch(e),
3529            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3530            Expression::Filter(e) => self.generate_filter(e),
3531            Expression::Float64(e) => self.generate_float64(e),
3532            Expression::ForIn(e) => self.generate_for_in(e),
3533            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3534            Expression::Format(e) => self.generate_format(e),
3535            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3536            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3537            Expression::From(e) => self.generate_from(e),
3538            Expression::FromBase(e) => self.generate_from_base(e),
3539            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3540            Expression::GapFill(e) => self.generate_gap_fill(e),
3541            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3542            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3543            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3544            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3545            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3546                self.generate_generated_as_identity_column_constraint(e)
3547            }
3548            Expression::GeneratedAsRowColumnConstraint(e) => {
3549                self.generate_generated_as_row_column_constraint(e)
3550            }
3551            Expression::Get(e) => self.generate_get(e),
3552            Expression::GetExtract(e) => self.generate_get_extract(e),
3553            Expression::Getbit(e) => self.generate_getbit(e),
3554            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3555            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3556            Expression::Group(e) => self.generate_group(e),
3557            Expression::GroupBy(e) => self.generate_group_by(e),
3558            Expression::Grouping(e) => self.generate_grouping(e),
3559            Expression::GroupingId(e) => self.generate_grouping_id(e),
3560            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3561            Expression::HashAgg(e) => self.generate_hash_agg(e),
3562            Expression::Having(e) => self.generate_having(e),
3563            Expression::HavingMax(e) => self.generate_having_max(e),
3564            Expression::Heredoc(e) => self.generate_heredoc(e),
3565            Expression::HexEncode(e) => self.generate_hex_encode(e),
3566            Expression::Hll(e) => self.generate_hll(e),
3567            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3568            Expression::IncludeProperty(e) => self.generate_include_property(e),
3569            Expression::Index(e) => self.generate_index(e),
3570            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3571            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3572            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3573            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3574            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3575            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3576            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3577            Expression::Install(e) => self.generate_install(e),
3578            Expression::IntervalOp(e) => self.generate_interval_op(e),
3579            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3580            Expression::IntoClause(e) => self.generate_into_clause(e),
3581            Expression::Introducer(e) => self.generate_introducer(e),
3582            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3583            Expression::JSON(e) => self.generate_json(e),
3584            Expression::JSONArray(e) => self.generate_json_array(e),
3585            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3586            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3587            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3588            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3589            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3590            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3591            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3592            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3593            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3594            Expression::JSONExists(e) => self.generate_json_exists(e),
3595            Expression::JSONCast(e) => self.generate_json_cast(e),
3596            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3597            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3598            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3599            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3600            Expression::JSONFormat(e) => self.generate_json_format(e),
3601            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3602            Expression::JSONKeys(e) => self.generate_json_keys(e),
3603            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3604            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3605            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3606            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3607            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3608            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3609            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3610            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3611            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3612            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3613            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3614            Expression::JSONRemove(e) => self.generate_json_remove(e),
3615            Expression::JSONSchema(e) => self.generate_json_schema(e),
3616            Expression::JSONSet(e) => self.generate_json_set(e),
3617            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3618            Expression::JSONTable(e) => self.generate_json_table(e),
3619            Expression::JSONType(e) => self.generate_json_type(e),
3620            Expression::JSONValue(e) => self.generate_json_value(e),
3621            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3622            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3623            Expression::JoinHint(e) => self.generate_join_hint(e),
3624            Expression::JournalProperty(e) => self.generate_journal_property(e),
3625            Expression::LanguageProperty(e) => self.generate_language_property(e),
3626            Expression::Lateral(e) => self.generate_lateral(e),
3627            Expression::LikeProperty(e) => self.generate_like_property(e),
3628            Expression::Limit(e) => self.generate_limit(e),
3629            Expression::LimitOptions(e) => self.generate_limit_options(e),
3630            Expression::List(e) => self.generate_list(e),
3631            Expression::ToMap(e) => self.generate_tomap(e),
3632            Expression::Localtime(e) => self.generate_localtime(e),
3633            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3634            Expression::LocationProperty(e) => self.generate_location_property(e),
3635            Expression::Lock(e) => self.generate_lock(e),
3636            Expression::LockProperty(e) => self.generate_lock_property(e),
3637            Expression::LockingProperty(e) => self.generate_locking_property(e),
3638            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3639            Expression::LogProperty(e) => self.generate_log_property(e),
3640            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3641            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3642            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3643            Expression::MakeInterval(e) => self.generate_make_interval(e),
3644            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3645            Expression::Map(e) => self.generate_map(e),
3646            Expression::MapCat(e) => self.generate_map_cat(e),
3647            Expression::MapDelete(e) => self.generate_map_delete(e),
3648            Expression::MapInsert(e) => self.generate_map_insert(e),
3649            Expression::MapPick(e) => self.generate_map_pick(e),
3650            Expression::MaskingPolicyColumnConstraint(e) => {
3651                self.generate_masking_policy_column_constraint(e)
3652            }
3653            Expression::MatchAgainst(e) => self.generate_match_against(e),
3654            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3655            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3656            Expression::Merge(e) => self.generate_merge(e),
3657            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3658            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3659            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3660            Expression::Minhash(e) => self.generate_minhash(e),
3661            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3662            Expression::Monthname(e) => self.generate_monthname(e),
3663            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3664            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3665            Expression::Normal(e) => self.generate_normal(e),
3666            Expression::Normalize(e) => self.generate_normalize(e),
3667            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3668            Expression::Nullif(e) => self.generate_nullif(e),
3669            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3670            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3671            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3672            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3673            Expression::Offset(e) => self.generate_offset(e),
3674            Expression::Qualify(e) => self.generate_qualify(e),
3675            Expression::OnCluster(e) => self.generate_on_cluster(e),
3676            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3677            Expression::OnCondition(e) => self.generate_on_condition(e),
3678            Expression::OnConflict(e) => self.generate_on_conflict(e),
3679            Expression::OnProperty(e) => self.generate_on_property(e),
3680            Expression::Opclass(e) => self.generate_opclass(e),
3681            Expression::OpenJSON(e) => self.generate_open_json(e),
3682            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3683            Expression::Operator(e) => self.generate_operator(e),
3684            Expression::OrderBy(e) => self.generate_order_by(e),
3685            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3686            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3687            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3688            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3689            Expression::ParseIp(e) => self.generate_parse_ip(e),
3690            Expression::ParseJSON(e) => self.generate_parse_json(e),
3691            Expression::ParseTime(e) => self.generate_parse_time(e),
3692            Expression::ParseUrl(e) => self.generate_parse_url(e),
3693            Expression::Partition(e) => self.generate_partition_expr(e),
3694            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3695            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3696            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3697            Expression::PartitionByRangePropertyDynamic(e) => {
3698                self.generate_partition_by_range_property_dynamic(e)
3699            }
3700            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3701            Expression::PartitionList(e) => self.generate_partition_list(e),
3702            Expression::PartitionRange(e) => self.generate_partition_range(e),
3703            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3704            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3705            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3706            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3707            Expression::PeriodForSystemTimeConstraint(e) => {
3708                self.generate_period_for_system_time_constraint(e)
3709            }
3710            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3711            Expression::PivotAny(e) => self.generate_pivot_any(e),
3712            Expression::Predict(e) => self.generate_predict(e),
3713            Expression::PreviousDay(e) => self.generate_previous_day(e),
3714            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3715            Expression::PrimaryKeyColumnConstraint(e) => {
3716                self.generate_primary_key_column_constraint(e)
3717            }
3718            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3719            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3720            Expression::OptionsProperty(e) => self.generate_options_property(e),
3721            Expression::Properties(e) => self.generate_properties(e),
3722            Expression::Property(e) => self.generate_property(e),
3723            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3724            Expression::Put(e) => self.generate_put(e),
3725            Expression::Quantile(e) => self.generate_quantile(e),
3726            Expression::QueryBand(e) => self.generate_query_band(e),
3727            Expression::QueryOption(e) => self.generate_query_option(e),
3728            Expression::QueryTransform(e) => self.generate_query_transform(e),
3729            Expression::Randn(e) => self.generate_randn(e),
3730            Expression::Randstr(e) => self.generate_randstr(e),
3731            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3732            Expression::RangeN(e) => self.generate_range_n(e),
3733            Expression::ReadCSV(e) => self.generate_read_csv(e),
3734            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3735            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3736            Expression::Reduce(e) => self.generate_reduce(e),
3737            Expression::Reference(e) => self.generate_reference(e),
3738            Expression::Refresh(e) => self.generate_refresh(e),
3739            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3740            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3741            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3742            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3743            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3744            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3745            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3746            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3747            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3748            Expression::RegrCount(e) => self.generate_regr_count(e),
3749            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3750            Expression::RegrR2(e) => self.generate_regr_r2(e),
3751            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3752            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3753            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3754            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3755            Expression::RegrValx(e) => self.generate_regr_valx(e),
3756            Expression::RegrValy(e) => self.generate_regr_valy(e),
3757            Expression::RemoteWithConnectionModelProperty(e) => {
3758                self.generate_remote_with_connection_model_property(e)
3759            }
3760            Expression::RenameColumn(e) => self.generate_rename_column(e),
3761            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3762            Expression::Returning(e) => self.generate_returning(e),
3763            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3764            Expression::Rollback(e) => self.generate_rollback(e),
3765            Expression::Rollup(e) => self.generate_rollup(e),
3766            Expression::RowFormatDelimitedProperty(e) => {
3767                self.generate_row_format_delimited_property(e)
3768            }
3769            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3770            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3771            Expression::SHA2(e) => self.generate_sha2(e),
3772            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3773            Expression::SafeAdd(e) => self.generate_safe_add(e),
3774            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3775            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3776            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3777            Expression::SampleProperty(e) => self.generate_sample_property(e),
3778            Expression::Schema(e) => self.generate_schema(e),
3779            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3780            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3781            Expression::Search(e) => self.generate_search(e),
3782            Expression::SearchIp(e) => self.generate_search_ip(e),
3783            Expression::SecurityProperty(e) => self.generate_security_property(e),
3784            Expression::SemanticView(e) => self.generate_semantic_view(e),
3785            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3786            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3787            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3788            Expression::Set(e) => self.generate_set(e),
3789            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3790            Expression::SetItem(e) => self.generate_set_item(e),
3791            Expression::SetOperation(e) => self.generate_set_operation(e),
3792            Expression::SetProperty(e) => self.generate_set_property(e),
3793            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3794            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3795            Expression::Slice(e) => self.generate_slice(e),
3796            Expression::SortArray(e) => self.generate_sort_array(e),
3797            Expression::SortBy(e) => self.generate_sort_by(e),
3798            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3799            Expression::SplitPart(e) => self.generate_split_part(e),
3800            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3801            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3802            Expression::StDistance(e) => self.generate_st_distance(e),
3803            Expression::StPoint(e) => self.generate_st_point(e),
3804            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3805            Expression::StandardHash(e) => self.generate_standard_hash(e),
3806            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3807            Expression::StrPosition(e) => self.generate_str_position(e),
3808            Expression::StrToDate(e) => self.generate_str_to_date(e),
3809            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3810            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3811            Expression::StrToMap(e) => self.generate_str_to_map(e),
3812            Expression::StrToTime(e) => self.generate_str_to_time(e),
3813            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3814            Expression::StringToArray(e) => self.generate_string_to_array(e),
3815            Expression::Struct(e) => self.generate_struct(e),
3816            Expression::Stuff(e) => self.generate_stuff(e),
3817            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3818            Expression::Summarize(e) => self.generate_summarize(e),
3819            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3820            Expression::TableAlias(e) => self.generate_table_alias(e),
3821            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3822            Expression::RowsFrom(e) => self.generate_rows_from(e),
3823            Expression::TableSample(e) => self.generate_table_sample(e),
3824            Expression::Tag(e) => self.generate_tag(e),
3825            Expression::Tags(e) => self.generate_tags(e),
3826            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3827            Expression::Time(e) => self.generate_time_func(e),
3828            Expression::TimeAdd(e) => self.generate_time_add(e),
3829            Expression::TimeDiff(e) => self.generate_time_diff(e),
3830            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3831            Expression::TimeSlice(e) => self.generate_time_slice(e),
3832            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3833            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3834            Expression::TimeSub(e) => self.generate_time_sub(e),
3835            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3836            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3837            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3838            Expression::TimeUnit(e) => self.generate_time_unit(e),
3839            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3840            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3841            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3842            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3843            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3844            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3845            Expression::ToBinary(e) => self.generate_to_binary(e),
3846            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3847            Expression::ToChar(e) => self.generate_to_char(e),
3848            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3849            Expression::ToDouble(e) => self.generate_to_double(e),
3850            Expression::ToFile(e) => self.generate_to_file(e),
3851            Expression::ToNumber(e) => self.generate_to_number(e),
3852            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3853            Expression::Transaction(e) => self.generate_transaction(e),
3854            Expression::Transform(e) => self.generate_transform(e),
3855            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3856            Expression::TransientProperty(e) => self.generate_transient_property(e),
3857            Expression::Translate(e) => self.generate_translate(e),
3858            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3859            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3860            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3861            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3862            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3863            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3864            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3865            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3866            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3867            Expression::Unhex(e) => self.generate_unhex(e),
3868            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3869            Expression::Uniform(e) => self.generate_uniform(e),
3870            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3871            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3872            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3873            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3874            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3875            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3876            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3877            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3878            Expression::UtcTime(e) => self.generate_utc_time(e),
3879            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3880            Expression::Uuid(e) => self.generate_uuid(e),
3881            Expression::Var(v) => {
3882                if matches!(self.config.dialect, Some(DialectType::MySQL))
3883                    && v.this.len() > 2
3884                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3885                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3886                {
3887                    return self.generate_identifier(&Identifier {
3888                        name: v.this.clone(),
3889                        quoted: true,
3890                        trailing_comments: Vec::new(),
3891                        span: None,
3892                    });
3893                }
3894                self.write(&v.this);
3895                Ok(())
3896            }
3897            Expression::Variadic(e) => {
3898                self.write_keyword("VARIADIC");
3899                self.write_space();
3900                self.generate_expression(&e.this)?;
3901                Ok(())
3902            }
3903            Expression::VarMap(e) => self.generate_var_map(e),
3904            Expression::VectorSearch(e) => self.generate_vector_search(e),
3905            Expression::Version(e) => self.generate_version(e),
3906            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3907            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3908            Expression::WatermarkColumnConstraint(e) => {
3909                self.generate_watermark_column_constraint(e)
3910            }
3911            Expression::Week(e) => self.generate_week(e),
3912            Expression::When(e) => self.generate_when(e),
3913            Expression::Whens(e) => self.generate_whens(e),
3914            Expression::Where(e) => self.generate_where(e),
3915            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3916            Expression::Window(e) => self.generate_window(e),
3917            Expression::WindowSpec(e) => self.generate_window_spec(e),
3918            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3919            Expression::WithFill(e) => self.generate_with_fill(e),
3920            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3921            Expression::WithOperator(e) => self.generate_with_operator(e),
3922            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3923            Expression::WithSchemaBindingProperty(e) => {
3924                self.generate_with_schema_binding_property(e)
3925            }
3926            Expression::WithSystemVersioningProperty(e) => {
3927                self.generate_with_system_versioning_property(e)
3928            }
3929            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3930            Expression::XMLElement(e) => self.generate_xml_element(e),
3931            Expression::XMLGet(e) => self.generate_xml_get(e),
3932            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3933            Expression::XMLTable(e) => self.generate_xml_table(e),
3934            Expression::Xor(e) => self.generate_xor(e),
3935            Expression::Zipf(e) => self.generate_zipf(e),
3936            _ => self.write_unsupported_comment("unsupported expression"),
3937        }
3938    }
3939
3940    fn generate_select(&mut self, select: &Select) -> Result<()> {
3941        use crate::dialects::DialectType;
3942
3943        // Output leading comments before SELECT
3944        for comment in &select.leading_comments {
3945            self.write_formatted_comment(comment);
3946            self.write(" ");
3947        }
3948
3949        // WITH clause
3950        if let Some(with) = &select.with {
3951            self.generate_with(with)?;
3952            if self.config.pretty {
3953                self.write_newline();
3954                self.write_indent();
3955            } else {
3956                self.write_space();
3957            }
3958        }
3959
3960        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3961        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3962        for comment in &select.post_select_comments {
3963            self.write_formatted_comment(comment);
3964            self.write(" ");
3965        }
3966
3967        self.write_keyword("SELECT");
3968
3969        // Generate query hint if present /*+ ... */
3970        if let Some(hint) = &select.hint {
3971            self.generate_hint(hint)?;
3972        }
3973
3974        // For SQL Server, convert LIMIT to TOP (structural transformation)
3975        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3976        // TOP clause (SQL Server style - before DISTINCT)
3977        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3978            && select.top.is_none()
3979            && select.limit.is_some()
3980            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3981
3982        // For TOP-supporting dialects: DISTINCT before TOP
3983        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3984        let is_top_dialect = matches!(
3985            self.config.dialect,
3986            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3987        );
3988        let keep_top_verbatim = !is_top_dialect
3989            && select.limit.is_none()
3990            && select
3991                .top
3992                .as_ref()
3993                .map_or(false, |top| top.percent || top.with_ties);
3994
3995        if select.distinct && (is_top_dialect || select.top.is_some()) {
3996            self.write_space();
3997            self.write_keyword("DISTINCT");
3998        }
3999
4000        if is_top_dialect || keep_top_verbatim {
4001            if let Some(top) = &select.top {
4002                self.write_space();
4003                self.write_keyword("TOP");
4004                if top.parenthesized {
4005                    self.write(" (");
4006                    self.generate_expression(&top.this)?;
4007                    self.write(")");
4008                } else {
4009                    self.write_space();
4010                    self.generate_expression(&top.this)?;
4011                }
4012                if top.percent {
4013                    self.write_space();
4014                    self.write_keyword("PERCENT");
4015                }
4016                if top.with_ties {
4017                    self.write_space();
4018                    self.write_keyword("WITH TIES");
4019                }
4020            } else if use_top_from_limit {
4021                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4022                if let Some(limit) = &select.limit {
4023                    self.write_space();
4024                    self.write_keyword("TOP");
4025                    // Use parentheses for complex expressions, but not for simple literals
4026                    let is_simple_literal =
4027                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
4028                    if is_simple_literal {
4029                        self.write_space();
4030                        self.generate_expression(&limit.this)?;
4031                    } else {
4032                        self.write(" (");
4033                        self.generate_expression(&limit.this)?;
4034                        self.write(")");
4035                    }
4036                }
4037            }
4038        }
4039
4040        if select.distinct && !is_top_dialect && select.top.is_none() {
4041            self.write_space();
4042            self.write_keyword("DISTINCT");
4043        }
4044
4045        // DISTINCT ON clause (PostgreSQL)
4046        if let Some(distinct_on) = &select.distinct_on {
4047            self.write_space();
4048            self.write_keyword("ON");
4049            self.write(" (");
4050            for (i, expr) in distinct_on.iter().enumerate() {
4051                if i > 0 {
4052                    self.write(", ");
4053                }
4054                self.generate_expression(expr)?;
4055            }
4056            self.write(")");
4057        }
4058
4059        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4060        for modifier in &select.operation_modifiers {
4061            self.write_space();
4062            self.write_keyword(modifier);
4063        }
4064
4065        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4066        if let Some(kind) = &select.kind {
4067            self.write_space();
4068            self.write_keyword("AS");
4069            self.write_space();
4070            self.write_keyword(kind);
4071        }
4072
4073        // Expressions (only if there are any)
4074        if !select.expressions.is_empty() {
4075            if self.config.pretty {
4076                self.write_newline();
4077                self.indent_level += 1;
4078            } else {
4079                self.write_space();
4080            }
4081        }
4082
4083        for (i, expr) in select.expressions.iter().enumerate() {
4084            if i > 0 {
4085                self.write(",");
4086                if self.config.pretty {
4087                    self.write_newline();
4088                } else {
4089                    self.write_space();
4090                }
4091            }
4092            if self.config.pretty {
4093                self.write_indent();
4094            }
4095            self.generate_expression(expr)?;
4096        }
4097
4098        if self.config.pretty && !select.expressions.is_empty() {
4099            self.indent_level -= 1;
4100        }
4101
4102        // INTO clause (SELECT ... INTO table_name)
4103        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4104        if let Some(into) = &select.into {
4105            if self.config.pretty {
4106                self.write_newline();
4107                self.write_indent();
4108            } else {
4109                self.write_space();
4110            }
4111            if into.bulk_collect {
4112                self.write_keyword("BULK COLLECT INTO");
4113            } else {
4114                self.write_keyword("INTO");
4115            }
4116            if into.temporary {
4117                self.write_space();
4118                self.write_keyword("TEMPORARY");
4119            }
4120            if into.unlogged {
4121                self.write_space();
4122                self.write_keyword("UNLOGGED");
4123            }
4124            self.write_space();
4125            // If we have multiple expressions, output them comma-separated
4126            if !into.expressions.is_empty() {
4127                for (i, expr) in into.expressions.iter().enumerate() {
4128                    if i > 0 {
4129                        self.write(", ");
4130                    }
4131                    self.generate_expression(expr)?;
4132                }
4133            } else {
4134                self.generate_expression(&into.this)?;
4135            }
4136        }
4137
4138        // FROM clause
4139        if let Some(from) = &select.from {
4140            if self.config.pretty {
4141                self.write_newline();
4142                self.write_indent();
4143            } else {
4144                self.write_space();
4145            }
4146            self.write_keyword("FROM");
4147            self.write_space();
4148
4149            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4150            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4151            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4152            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4153            let has_tablesample = from
4154                .expressions
4155                .iter()
4156                .any(|e| matches!(e, Expression::TableSample(_)));
4157            let is_cross_join_dialect = matches!(
4158                self.config.dialect,
4159                Some(DialectType::BigQuery)
4160                    | Some(DialectType::Hive)
4161                    | Some(DialectType::Spark)
4162                    | Some(DialectType::Databricks)
4163                    | Some(DialectType::SQLite)
4164                    | Some(DialectType::ClickHouse)
4165            );
4166            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4167            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4168            let source_is_same_as_target = self.config.source_dialect.is_some()
4169                && self.config.source_dialect == self.config.dialect;
4170            let source_is_cross_join_dialect = matches!(
4171                self.config.source_dialect,
4172                Some(DialectType::BigQuery)
4173                    | Some(DialectType::Hive)
4174                    | Some(DialectType::Spark)
4175                    | Some(DialectType::Databricks)
4176                    | Some(DialectType::SQLite)
4177                    | Some(DialectType::ClickHouse)
4178            );
4179            let use_cross_join = !has_tablesample
4180                && is_cross_join_dialect
4181                && (source_is_same_as_target
4182                    || source_is_cross_join_dialect
4183                    || self.config.source_dialect.is_none());
4184
4185            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4186            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4187
4188            for (i, expr) in from.expressions.iter().enumerate() {
4189                if i > 0 {
4190                    if use_cross_join {
4191                        self.write(" CROSS JOIN ");
4192                    } else {
4193                        self.write(", ");
4194                    }
4195                }
4196                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4197                    self.write("(");
4198                    self.generate_expression(expr)?;
4199                    self.write(")");
4200                } else {
4201                    self.generate_expression(expr)?;
4202                }
4203            }
4204        }
4205
4206        // JOINs - handle nested join structure for pretty printing
4207        // Deferred-condition joins "own" the non-deferred joins that follow them
4208        // until the next deferred join or end of list
4209        if self.config.pretty {
4210            self.generate_joins_with_nesting(&select.joins)?;
4211        } else {
4212            for join in &select.joins {
4213                self.generate_join(join)?;
4214            }
4215            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4216            for join in select.joins.iter().rev() {
4217                if join.deferred_condition {
4218                    self.generate_join_condition(join)?;
4219                }
4220            }
4221        }
4222
4223        // LATERAL VIEW clauses (Hive/Spark)
4224        for lateral_view in &select.lateral_views {
4225            self.generate_lateral_view(lateral_view)?;
4226        }
4227
4228        // PREWHERE (ClickHouse)
4229        if let Some(prewhere) = &select.prewhere {
4230            self.write_clause_condition("PREWHERE", prewhere)?;
4231        }
4232
4233        // WHERE
4234        if let Some(where_clause) = &select.where_clause {
4235            self.write_clause_condition("WHERE", &where_clause.this)?;
4236        }
4237
4238        // CONNECT BY (Oracle hierarchical queries)
4239        if let Some(connect) = &select.connect {
4240            self.generate_connect(connect)?;
4241        }
4242
4243        // GROUP BY
4244        if let Some(group_by) = &select.group_by {
4245            if self.config.pretty {
4246                // Output leading comments on their own lines before GROUP BY
4247                for comment in &group_by.comments {
4248                    self.write_newline();
4249                    self.write_indent();
4250                    self.write_formatted_comment(comment);
4251                }
4252                self.write_newline();
4253                self.write_indent();
4254            } else {
4255                self.write_space();
4256                // In non-pretty mode, output comments inline
4257                for comment in &group_by.comments {
4258                    self.write_formatted_comment(comment);
4259                    self.write_space();
4260                }
4261            }
4262            self.write_keyword("GROUP BY");
4263            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4264            match group_by.all {
4265                Some(true) => {
4266                    self.write_space();
4267                    self.write_keyword("ALL");
4268                }
4269                Some(false) => {
4270                    self.write_space();
4271                    self.write_keyword("DISTINCT");
4272                }
4273                None => {}
4274            }
4275            if !group_by.expressions.is_empty() {
4276                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4277                // These are represented as Cube/Rollup expressions with empty expressions at the end
4278                let mut trailing_cube = false;
4279                let mut trailing_rollup = false;
4280                let mut plain_expressions: Vec<&Expression> = Vec::new();
4281                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4282                let mut cube_expressions: Vec<&Expression> = Vec::new();
4283                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4284
4285                for expr in &group_by.expressions {
4286                    match expr {
4287                        Expression::Cube(c) if c.expressions.is_empty() => {
4288                            trailing_cube = true;
4289                        }
4290                        Expression::Rollup(r) if r.expressions.is_empty() => {
4291                            trailing_rollup = true;
4292                        }
4293                        Expression::Function(f) if f.name == "CUBE" => {
4294                            cube_expressions.push(expr);
4295                        }
4296                        Expression::Function(f) if f.name == "ROLLUP" => {
4297                            rollup_expressions.push(expr);
4298                        }
4299                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4300                            grouping_sets_expressions.push(expr);
4301                        }
4302                        _ => {
4303                            plain_expressions.push(expr);
4304                        }
4305                    }
4306                }
4307
4308                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4309                let mut regular_expressions: Vec<&Expression> = Vec::new();
4310                regular_expressions.extend(plain_expressions);
4311                regular_expressions.extend(grouping_sets_expressions);
4312                regular_expressions.extend(cube_expressions);
4313                regular_expressions.extend(rollup_expressions);
4314
4315                if self.config.pretty {
4316                    self.write_newline();
4317                    self.indent_level += 1;
4318                    self.write_indent();
4319                } else {
4320                    self.write_space();
4321                }
4322
4323                for (i, expr) in regular_expressions.iter().enumerate() {
4324                    if i > 0 {
4325                        if self.config.pretty {
4326                            self.write(",");
4327                            self.write_newline();
4328                            self.write_indent();
4329                        } else {
4330                            self.write(", ");
4331                        }
4332                    }
4333                    self.generate_expression(expr)?;
4334                }
4335
4336                if self.config.pretty {
4337                    self.indent_level -= 1;
4338                }
4339
4340                // Output trailing WITH CUBE or WITH ROLLUP
4341                if trailing_cube {
4342                    self.write_space();
4343                    self.write_keyword("WITH CUBE");
4344                } else if trailing_rollup {
4345                    self.write_space();
4346                    self.write_keyword("WITH ROLLUP");
4347                }
4348            }
4349
4350            // ClickHouse: WITH TOTALS
4351            if group_by.totals {
4352                self.write_space();
4353                self.write_keyword("WITH TOTALS");
4354            }
4355        }
4356
4357        // HAVING
4358        if let Some(having) = &select.having {
4359            if self.config.pretty {
4360                // Output leading comments on their own lines before HAVING
4361                for comment in &having.comments {
4362                    self.write_newline();
4363                    self.write_indent();
4364                    self.write_formatted_comment(comment);
4365                }
4366            } else {
4367                for comment in &having.comments {
4368                    self.write_space();
4369                    self.write_formatted_comment(comment);
4370                }
4371            }
4372            self.write_clause_condition("HAVING", &having.this)?;
4373        }
4374
4375        // QUALIFY and WINDOW clause ordering depends on input SQL
4376        if select.qualify_after_window {
4377            // WINDOW before QUALIFY (DuckDB style)
4378            if let Some(windows) = &select.windows {
4379                self.write_window_clause(windows)?;
4380            }
4381            if let Some(qualify) = &select.qualify {
4382                self.write_clause_condition("QUALIFY", &qualify.this)?;
4383            }
4384        } else {
4385            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4386            if let Some(qualify) = &select.qualify {
4387                self.write_clause_condition("QUALIFY", &qualify.this)?;
4388            }
4389            if let Some(windows) = &select.windows {
4390                self.write_window_clause(windows)?;
4391            }
4392        }
4393
4394        // DISTRIBUTE BY (Hive/Spark)
4395        if let Some(distribute_by) = &select.distribute_by {
4396            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4397        }
4398
4399        // CLUSTER BY (Hive/Spark)
4400        if let Some(cluster_by) = &select.cluster_by {
4401            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4402        }
4403
4404        // SORT BY (Hive/Spark - comes before ORDER BY)
4405        if let Some(sort_by) = &select.sort_by {
4406            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4407        }
4408
4409        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4410        if let Some(order_by) = &select.order_by {
4411            if self.config.pretty {
4412                // Output leading comments on their own lines before ORDER BY
4413                for comment in &order_by.comments {
4414                    self.write_newline();
4415                    self.write_indent();
4416                    self.write_formatted_comment(comment);
4417                }
4418            } else {
4419                for comment in &order_by.comments {
4420                    self.write_space();
4421                    self.write_formatted_comment(comment);
4422                }
4423            }
4424            let keyword = if order_by.siblings {
4425                "ORDER SIBLINGS BY"
4426            } else {
4427                "ORDER BY"
4428            };
4429            self.write_order_clause(keyword, &order_by.expressions)?;
4430        }
4431
4432        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4433        if select.order_by.is_none()
4434            && select.fetch.is_some()
4435            && matches!(
4436                self.config.dialect,
4437                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4438            )
4439        {
4440            if self.config.pretty {
4441                self.write_newline();
4442                self.write_indent();
4443            } else {
4444                self.write_space();
4445            }
4446            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4447        }
4448
4449        // LIMIT and OFFSET
4450        // PostgreSQL and others use: LIMIT count OFFSET offset
4451        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4452        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4453        let is_presto_like = matches!(
4454            self.config.dialect,
4455            Some(DialectType::Presto) | Some(DialectType::Trino)
4456        );
4457
4458        if is_presto_like && select.offset.is_some() {
4459            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4460            if let Some(offset) = &select.offset {
4461                if self.config.pretty {
4462                    self.write_newline();
4463                    self.write_indent();
4464                } else {
4465                    self.write_space();
4466                }
4467                self.write_keyword("OFFSET");
4468                self.write_space();
4469                self.write_limit_expr(&offset.this)?;
4470                if offset.rows == Some(true) {
4471                    self.write_space();
4472                    self.write_keyword("ROWS");
4473                }
4474            }
4475            if let Some(limit) = &select.limit {
4476                if self.config.pretty {
4477                    self.write_newline();
4478                    self.write_indent();
4479                } else {
4480                    self.write_space();
4481                }
4482                self.write_keyword("LIMIT");
4483                self.write_space();
4484                self.write_limit_expr(&limit.this)?;
4485                if limit.percent {
4486                    self.write_space();
4487                    self.write_keyword("PERCENT");
4488                }
4489                // Emit any comments that were captured from before the LIMIT keyword
4490                for comment in &limit.comments {
4491                    self.write(" ");
4492                    self.write_formatted_comment(comment);
4493                }
4494            }
4495        } else {
4496            // Check if FETCH will be converted to LIMIT (used for ordering)
4497            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4498                !fetch.percent
4499                    && !fetch.with_ties
4500                    && fetch.count.is_some()
4501                    && matches!(
4502                        self.config.dialect,
4503                        Some(DialectType::Spark)
4504                            | Some(DialectType::Hive)
4505                            | Some(DialectType::DuckDB)
4506                            | Some(DialectType::SQLite)
4507                            | Some(DialectType::MySQL)
4508                            | Some(DialectType::BigQuery)
4509                            | Some(DialectType::Databricks)
4510                            | Some(DialectType::StarRocks)
4511                            | Some(DialectType::Doris)
4512                            | Some(DialectType::Athena)
4513                            | Some(DialectType::ClickHouse)
4514                            | Some(DialectType::Redshift)
4515                    )
4516            });
4517
4518            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4519            if let Some(limit) = &select.limit {
4520                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4521                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4522                    if self.config.pretty {
4523                        self.write_newline();
4524                        self.write_indent();
4525                    } else {
4526                        self.write_space();
4527                    }
4528                    self.write_keyword("LIMIT");
4529                    self.write_space();
4530                    self.write_limit_expr(&limit.this)?;
4531                    if limit.percent {
4532                        self.write_space();
4533                        self.write_keyword("PERCENT");
4534                    }
4535                    // Emit any comments that were captured from before the LIMIT keyword
4536                    for comment in &limit.comments {
4537                        self.write(" ");
4538                        self.write_formatted_comment(comment);
4539                    }
4540                }
4541            }
4542
4543            // Convert TOP to LIMIT for non-TOP dialects
4544            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4545                if let Some(top) = &select.top {
4546                    if !top.percent && !top.with_ties {
4547                        if self.config.pretty {
4548                            self.write_newline();
4549                            self.write_indent();
4550                        } else {
4551                            self.write_space();
4552                        }
4553                        self.write_keyword("LIMIT");
4554                        self.write_space();
4555                        self.generate_expression(&top.this)?;
4556                    }
4557                }
4558            }
4559
4560            // If FETCH will be converted to LIMIT and there's also OFFSET,
4561            // emit LIMIT from FETCH BEFORE the OFFSET
4562            if fetch_as_limit && select.offset.is_some() {
4563                if let Some(fetch) = &select.fetch {
4564                    if self.config.pretty {
4565                        self.write_newline();
4566                        self.write_indent();
4567                    } else {
4568                        self.write_space();
4569                    }
4570                    self.write_keyword("LIMIT");
4571                    self.write_space();
4572                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4573                }
4574            }
4575
4576            // OFFSET
4577            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4578            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4579            if let Some(offset) = &select.offset {
4580                if self.config.pretty {
4581                    self.write_newline();
4582                    self.write_indent();
4583                } else {
4584                    self.write_space();
4585                }
4586                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4587                    // SQL Server 2012+ OFFSET ... FETCH syntax
4588                    self.write_keyword("OFFSET");
4589                    self.write_space();
4590                    self.write_limit_expr(&offset.this)?;
4591                    self.write_space();
4592                    self.write_keyword("ROWS");
4593                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4594                    if let Some(limit) = &select.limit {
4595                        self.write_space();
4596                        self.write_keyword("FETCH NEXT");
4597                        self.write_space();
4598                        self.write_limit_expr(&limit.this)?;
4599                        self.write_space();
4600                        self.write_keyword("ROWS ONLY");
4601                    }
4602                } else {
4603                    self.write_keyword("OFFSET");
4604                    self.write_space();
4605                    self.write_limit_expr(&offset.this)?;
4606                    // Output ROWS keyword if it was in the original SQL
4607                    if offset.rows == Some(true) {
4608                        self.write_space();
4609                        self.write_keyword("ROWS");
4610                    }
4611                }
4612            }
4613        }
4614
4615        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4616        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4617            if let Some(limit_by) = &select.limit_by {
4618                if !limit_by.is_empty() {
4619                    self.write_space();
4620                    self.write_keyword("BY");
4621                    self.write_space();
4622                    for (i, expr) in limit_by.iter().enumerate() {
4623                        if i > 0 {
4624                            self.write(", ");
4625                        }
4626                        self.generate_expression(expr)?;
4627                    }
4628                }
4629            }
4630        }
4631
4632        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4633        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4634            if let Some(settings) = &select.settings {
4635                if self.config.pretty {
4636                    self.write_newline();
4637                    self.write_indent();
4638                } else {
4639                    self.write_space();
4640                }
4641                self.write_keyword("SETTINGS");
4642                self.write_space();
4643                for (i, expr) in settings.iter().enumerate() {
4644                    if i > 0 {
4645                        self.write(", ");
4646                    }
4647                    self.generate_expression(expr)?;
4648                }
4649            }
4650
4651            if let Some(format_expr) = &select.format {
4652                if self.config.pretty {
4653                    self.write_newline();
4654                    self.write_indent();
4655                } else {
4656                    self.write_space();
4657                }
4658                self.write_keyword("FORMAT");
4659                self.write_space();
4660                self.generate_expression(format_expr)?;
4661            }
4662        }
4663
4664        // FETCH FIRST/NEXT
4665        if let Some(fetch) = &select.fetch {
4666            // Check if we already emitted LIMIT from FETCH before OFFSET
4667            let fetch_already_as_limit = select.offset.is_some()
4668                && !fetch.percent
4669                && !fetch.with_ties
4670                && fetch.count.is_some()
4671                && matches!(
4672                    self.config.dialect,
4673                    Some(DialectType::Spark)
4674                        | Some(DialectType::Hive)
4675                        | Some(DialectType::DuckDB)
4676                        | Some(DialectType::SQLite)
4677                        | Some(DialectType::MySQL)
4678                        | Some(DialectType::BigQuery)
4679                        | Some(DialectType::Databricks)
4680                        | Some(DialectType::StarRocks)
4681                        | Some(DialectType::Doris)
4682                        | Some(DialectType::Athena)
4683                        | Some(DialectType::ClickHouse)
4684                        | Some(DialectType::Redshift)
4685                );
4686
4687            if fetch_already_as_limit {
4688                // Already emitted as LIMIT before OFFSET, skip
4689            } else {
4690                if self.config.pretty {
4691                    self.write_newline();
4692                    self.write_indent();
4693                } else {
4694                    self.write_space();
4695                }
4696
4697                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4698                let use_limit = !fetch.percent
4699                    && !fetch.with_ties
4700                    && fetch.count.is_some()
4701                    && matches!(
4702                        self.config.dialect,
4703                        Some(DialectType::Spark)
4704                            | Some(DialectType::Hive)
4705                            | Some(DialectType::DuckDB)
4706                            | Some(DialectType::SQLite)
4707                            | Some(DialectType::MySQL)
4708                            | Some(DialectType::BigQuery)
4709                            | Some(DialectType::Databricks)
4710                            | Some(DialectType::StarRocks)
4711                            | Some(DialectType::Doris)
4712                            | Some(DialectType::Athena)
4713                            | Some(DialectType::ClickHouse)
4714                            | Some(DialectType::Redshift)
4715                    );
4716
4717                if use_limit {
4718                    self.write_keyword("LIMIT");
4719                    self.write_space();
4720                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4721                } else {
4722                    self.write_keyword("FETCH");
4723                    self.write_space();
4724                    self.write_keyword(&fetch.direction);
4725                    if let Some(ref count) = fetch.count {
4726                        self.write_space();
4727                        self.generate_expression(count)?;
4728                    }
4729                    if fetch.percent {
4730                        self.write_space();
4731                        self.write_keyword("PERCENT");
4732                    }
4733                    if fetch.rows {
4734                        self.write_space();
4735                        self.write_keyword("ROWS");
4736                    }
4737                    if fetch.with_ties {
4738                        self.write_space();
4739                        self.write_keyword("WITH TIES");
4740                    } else {
4741                        self.write_space();
4742                        self.write_keyword("ONLY");
4743                    }
4744                }
4745            } // close fetch_already_as_limit else
4746        }
4747
4748        // SAMPLE / TABLESAMPLE
4749        if let Some(sample) = &select.sample {
4750            use crate::dialects::DialectType;
4751            if self.config.pretty {
4752                self.write_newline();
4753            } else {
4754                self.write_space();
4755            }
4756
4757            if sample.is_using_sample {
4758                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4759                self.write_keyword("USING SAMPLE");
4760                self.generate_sample_body(sample)?;
4761            } else {
4762                self.write_keyword("TABLESAMPLE");
4763
4764                // Snowflake defaults to BERNOULLI when no explicit method is given
4765                let snowflake_bernoulli =
4766                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4767                        && !sample.explicit_method;
4768                if snowflake_bernoulli {
4769                    self.write_space();
4770                    self.write_keyword("BERNOULLI");
4771                }
4772
4773                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4774                if matches!(sample.method, SampleMethod::Bucket) {
4775                    self.write_space();
4776                    self.write("(");
4777                    self.write_keyword("BUCKET");
4778                    self.write_space();
4779                    if let Some(ref num) = sample.bucket_numerator {
4780                        self.generate_expression(num)?;
4781                    }
4782                    self.write_space();
4783                    self.write_keyword("OUT OF");
4784                    self.write_space();
4785                    if let Some(ref denom) = sample.bucket_denominator {
4786                        self.generate_expression(denom)?;
4787                    }
4788                    if let Some(ref field) = sample.bucket_field {
4789                        self.write_space();
4790                        self.write_keyword("ON");
4791                        self.write_space();
4792                        self.generate_expression(field)?;
4793                    }
4794                    self.write(")");
4795                } else if sample.unit_after_size {
4796                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4797                    if sample.explicit_method && sample.method_before_size {
4798                        self.write_space();
4799                        match sample.method {
4800                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4801                            SampleMethod::System => self.write_keyword("SYSTEM"),
4802                            SampleMethod::Block => self.write_keyword("BLOCK"),
4803                            SampleMethod::Row => self.write_keyword("ROW"),
4804                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4805                            _ => {}
4806                        }
4807                    }
4808                    self.write(" (");
4809                    self.generate_expression(&sample.size)?;
4810                    self.write_space();
4811                    match sample.method {
4812                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4813                        SampleMethod::Row => self.write_keyword("ROWS"),
4814                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4815                        _ => {
4816                            self.write_keyword("PERCENT");
4817                        }
4818                    }
4819                    self.write(")");
4820                } else {
4821                    // Syntax: TABLESAMPLE METHOD (size)
4822                    self.write_space();
4823                    match sample.method {
4824                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4825                        SampleMethod::System => self.write_keyword("SYSTEM"),
4826                        SampleMethod::Block => self.write_keyword("BLOCK"),
4827                        SampleMethod::Row => self.write_keyword("ROW"),
4828                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4829                        SampleMethod::Bucket => {}
4830                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4831                    }
4832                    self.write(" (");
4833                    self.generate_expression(&sample.size)?;
4834                    if matches!(sample.method, SampleMethod::Percent) {
4835                        self.write_space();
4836                        self.write_keyword("PERCENT");
4837                    }
4838                    self.write(")");
4839                }
4840            }
4841
4842            if let Some(seed) = &sample.seed {
4843                self.write_space();
4844                // Databricks/Spark use REPEATABLE, not SEED
4845                let use_seed = sample.use_seed_keyword
4846                    && !matches!(
4847                        self.config.dialect,
4848                        Some(crate::dialects::DialectType::Databricks)
4849                            | Some(crate::dialects::DialectType::Spark)
4850                    );
4851                if use_seed {
4852                    self.write_keyword("SEED");
4853                } else {
4854                    self.write_keyword("REPEATABLE");
4855                }
4856                self.write(" (");
4857                self.generate_expression(seed)?;
4858                self.write(")");
4859            }
4860        }
4861
4862        // FOR UPDATE/SHARE locks
4863        // Skip locking clauses for dialects that don't support them
4864        if self.config.locking_reads_supported {
4865            for lock in &select.locks {
4866                if self.config.pretty {
4867                    self.write_newline();
4868                    self.write_indent();
4869                } else {
4870                    self.write_space();
4871                }
4872                self.generate_lock(lock)?;
4873            }
4874        }
4875
4876        // FOR XML clause (T-SQL)
4877        if !select.for_xml.is_empty() {
4878            if self.config.pretty {
4879                self.write_newline();
4880                self.write_indent();
4881            } else {
4882                self.write_space();
4883            }
4884            self.write_keyword("FOR XML");
4885            for (i, opt) in select.for_xml.iter().enumerate() {
4886                if self.config.pretty {
4887                    if i > 0 {
4888                        self.write(",");
4889                    }
4890                    self.write_newline();
4891                    self.write_indent();
4892                    self.write("  "); // extra indent for options
4893                } else {
4894                    if i > 0 {
4895                        self.write(",");
4896                    }
4897                    self.write_space();
4898                }
4899                self.generate_for_xml_option(opt)?;
4900            }
4901        }
4902
4903        // TSQL: OPTION clause
4904        if let Some(ref option) = select.option {
4905            if matches!(
4906                self.config.dialect,
4907                Some(crate::dialects::DialectType::TSQL)
4908                    | Some(crate::dialects::DialectType::Fabric)
4909            ) {
4910                self.write_space();
4911                self.write(option);
4912            }
4913        }
4914
4915        Ok(())
4916    }
4917
4918    /// Generate a single FOR XML option
4919    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4920        match opt {
4921            Expression::QueryOption(qo) => {
4922                // Extract the option name from Var
4923                if let Expression::Var(var) = &*qo.this {
4924                    self.write(&var.this);
4925                } else {
4926                    self.generate_expression(&qo.this)?;
4927                }
4928                // If there's an expression (like PATH('element')), output it in parens
4929                if let Some(expr) = &qo.expression {
4930                    self.write("(");
4931                    self.generate_expression(expr)?;
4932                    self.write(")");
4933                }
4934            }
4935            _ => {
4936                self.generate_expression(opt)?;
4937            }
4938        }
4939        Ok(())
4940    }
4941
4942    fn generate_with(&mut self, with: &With) -> Result<()> {
4943        use crate::dialects::DialectType;
4944
4945        // Output leading comments before WITH
4946        for comment in &with.leading_comments {
4947            self.write_formatted_comment(comment);
4948            self.write(" ");
4949        }
4950        self.write_keyword("WITH");
4951        if with.recursive && self.config.cte_recursive_keyword_required {
4952            self.write_space();
4953            self.write_keyword("RECURSIVE");
4954        }
4955        self.write_space();
4956
4957        // BigQuery doesn't support column aliases in CTE definitions
4958        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4959
4960        for (i, cte) in with.ctes.iter().enumerate() {
4961            if i > 0 {
4962                self.write(",");
4963                if self.config.pretty {
4964                    self.write_space();
4965                } else {
4966                    self.write(" ");
4967                }
4968            }
4969            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4970                self.generate_expression(&cte.this)?;
4971                self.write_space();
4972                self.write_keyword("AS");
4973                self.write_space();
4974                self.generate_identifier(&cte.alias)?;
4975                continue;
4976            }
4977            self.generate_identifier(&cte.alias)?;
4978            // Output CTE comments after alias name, before AS
4979            for comment in &cte.comments {
4980                self.write_space();
4981                self.write_formatted_comment(comment);
4982            }
4983            if !cte.columns.is_empty() && !skip_cte_columns {
4984                self.write("(");
4985                for (j, col) in cte.columns.iter().enumerate() {
4986                    if j > 0 {
4987                        self.write(", ");
4988                    }
4989                    self.generate_identifier(col)?;
4990                }
4991                self.write(")");
4992            }
4993            // USING KEY (columns) for DuckDB recursive CTEs
4994            if !cte.key_expressions.is_empty() {
4995                self.write_space();
4996                self.write_keyword("USING KEY");
4997                self.write(" (");
4998                for (i, key) in cte.key_expressions.iter().enumerate() {
4999                    if i > 0 {
5000                        self.write(", ");
5001                    }
5002                    self.generate_identifier(key)?;
5003                }
5004                self.write(")");
5005            }
5006            self.write_space();
5007            self.write_keyword("AS");
5008            // MATERIALIZED / NOT MATERIALIZED
5009            if let Some(materialized) = cte.materialized {
5010                self.write_space();
5011                if materialized {
5012                    self.write_keyword("MATERIALIZED");
5013                } else {
5014                    self.write_keyword("NOT MATERIALIZED");
5015                }
5016            }
5017            self.write(" (");
5018            if self.config.pretty {
5019                self.write_newline();
5020                self.indent_level += 1;
5021                self.write_indent();
5022            }
5023            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5024            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5025            let wrap_values_in_select = matches!(
5026                self.config.dialect,
5027                Some(DialectType::Spark) | Some(DialectType::Databricks)
5028            ) && matches!(&cte.this, Expression::Values(_));
5029
5030            if wrap_values_in_select {
5031                self.write_keyword("SELECT");
5032                self.write(" * ");
5033                self.write_keyword("FROM");
5034                self.write_space();
5035            }
5036            self.generate_expression(&cte.this)?;
5037            if self.config.pretty {
5038                self.write_newline();
5039                self.indent_level -= 1;
5040                self.write_indent();
5041            }
5042            self.write(")");
5043        }
5044
5045        // Generate SEARCH/CYCLE clause if present
5046        if let Some(search) = &with.search {
5047            self.write_space();
5048            self.generate_expression(search)?;
5049        }
5050
5051        Ok(())
5052    }
5053
5054    /// Generate joins with proper nesting structure for pretty printing.
5055    /// Deferred-condition joins "own" the non-deferred joins that follow them
5056    /// within the same nesting_group.
5057    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5058        let mut i = 0;
5059        while i < joins.len() {
5060            if joins[i].deferred_condition {
5061                let parent_group = joins[i].nesting_group;
5062
5063                // This join owns the following non-deferred joins in the same nesting_group
5064                // First output the join keyword and table (without condition)
5065                self.generate_join_without_condition(&joins[i])?;
5066
5067                // Find the range of child joins: same nesting_group and not deferred
5068                let child_start = i + 1;
5069                let mut child_end = child_start;
5070                while child_end < joins.len()
5071                    && !joins[child_end].deferred_condition
5072                    && joins[child_end].nesting_group == parent_group
5073                {
5074                    child_end += 1;
5075                }
5076
5077                // Output child joins with extra indentation
5078                if child_start < child_end {
5079                    self.indent_level += 1;
5080                    for j in child_start..child_end {
5081                        self.generate_join(&joins[j])?;
5082                    }
5083                    self.indent_level -= 1;
5084                }
5085
5086                // Output the deferred condition at the parent level
5087                self.generate_join_condition(&joins[i])?;
5088
5089                i = child_end;
5090            } else {
5091                // Regular join (no nesting)
5092                self.generate_join(&joins[i])?;
5093                i += 1;
5094            }
5095        }
5096        Ok(())
5097    }
5098
5099    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5100    /// Used for deferred-condition joins where the condition is output after child joins.
5101    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5102        // Save and temporarily clear the condition to prevent generate_join from outputting it
5103        // We achieve this by creating a modified copy
5104        let mut join_copy = join.clone();
5105        join_copy.on = None;
5106        join_copy.using = Vec::new();
5107        join_copy.deferred_condition = false;
5108        self.generate_join(&join_copy)
5109    }
5110
5111    fn generate_join(&mut self, join: &Join) -> Result<()> {
5112        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5113        if join.kind == JoinKind::Implicit {
5114            self.write(",");
5115            if self.config.pretty {
5116                self.write_newline();
5117                self.write_indent();
5118            } else {
5119                self.write_space();
5120            }
5121            self.generate_expression(&join.this)?;
5122            return Ok(());
5123        }
5124
5125        if self.config.pretty {
5126            self.write_newline();
5127            self.write_indent();
5128        } else {
5129            self.write_space();
5130        }
5131
5132        // Helper: format hint suffix (e.g., " LOOP" or "")
5133        // Only include join hints for dialects that support them
5134        let hint_str = if self.config.join_hints {
5135            join.join_hint
5136                .as_ref()
5137                .map(|h| format!(" {}", h))
5138                .unwrap_or_default()
5139        } else {
5140            String::new()
5141        };
5142
5143        let clickhouse_join_keyword =
5144            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5145                if let Some(hint) = &join.join_hint {
5146                    let mut global = false;
5147                    let mut strictness: Option<&'static str> = None;
5148                    for part in hint.split_whitespace() {
5149                        match part.to_uppercase().as_str() {
5150                            "GLOBAL" => global = true,
5151                            "ANY" => strictness = Some("ANY"),
5152                            "ASOF" => strictness = Some("ASOF"),
5153                            "SEMI" => strictness = Some("SEMI"),
5154                            "ANTI" => strictness = Some("ANTI"),
5155                            _ => {}
5156                        }
5157                    }
5158
5159                    if global || strictness.is_some() {
5160                        let join_type = match join.kind {
5161                            JoinKind::Left => {
5162                                if join.use_outer_keyword {
5163                                    "LEFT OUTER"
5164                                } else if join.use_inner_keyword {
5165                                    "LEFT INNER"
5166                                } else {
5167                                    "LEFT"
5168                                }
5169                            }
5170                            JoinKind::Right => {
5171                                if join.use_outer_keyword {
5172                                    "RIGHT OUTER"
5173                                } else if join.use_inner_keyword {
5174                                    "RIGHT INNER"
5175                                } else {
5176                                    "RIGHT"
5177                                }
5178                            }
5179                            JoinKind::Full => {
5180                                if join.use_outer_keyword {
5181                                    "FULL OUTER"
5182                                } else {
5183                                    "FULL"
5184                                }
5185                            }
5186                            JoinKind::Inner => {
5187                                if join.use_inner_keyword {
5188                                    "INNER"
5189                                } else {
5190                                    ""
5191                                }
5192                            }
5193                            _ => "",
5194                        };
5195
5196                        let mut parts = Vec::new();
5197                        if global {
5198                            parts.push("GLOBAL");
5199                        }
5200                        if !join_type.is_empty() {
5201                            parts.push(join_type);
5202                        }
5203                        if let Some(strict) = strictness {
5204                            parts.push(strict);
5205                        }
5206                        parts.push("JOIN");
5207                        Some(parts.join(" "))
5208                    } else {
5209                        None
5210                    }
5211                } else {
5212                    None
5213                }
5214            } else {
5215                None
5216            };
5217
5218        // Output any comments associated with this join
5219        // In pretty mode, comments go on their own line before the join keyword
5220        // In non-pretty mode, comments go inline before the join keyword
5221        if !join.comments.is_empty() {
5222            if self.config.pretty {
5223                // In pretty mode, go back before the newline+indent we just wrote
5224                // and output comments on their own lines
5225                // We need to output comments BEFORE the join keyword on separate lines
5226                // Trim the trailing newline+indent we already wrote
5227                let trimmed = self.output.trim_end().len();
5228                self.output.truncate(trimmed);
5229                for comment in &join.comments {
5230                    self.write_newline();
5231                    self.write_indent();
5232                    self.write_formatted_comment(comment);
5233                }
5234                self.write_newline();
5235                self.write_indent();
5236            } else {
5237                for comment in &join.comments {
5238                    self.write_formatted_comment(comment);
5239                    self.write_space();
5240                }
5241            }
5242        }
5243
5244        let directed_str = if join.directed { " DIRECTED" } else { "" };
5245
5246        if let Some(keyword) = clickhouse_join_keyword {
5247            self.write_keyword(&keyword);
5248        } else {
5249            match join.kind {
5250                JoinKind::Inner => {
5251                    if join.use_inner_keyword {
5252                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5253                    } else {
5254                        self.write_keyword(&format!(
5255                            "{}{}JOIN",
5256                            if hint_str.is_empty() {
5257                                String::new()
5258                            } else {
5259                                format!("{} ", hint_str.trim())
5260                            },
5261                            if directed_str.is_empty() {
5262                                ""
5263                            } else {
5264                                "DIRECTED "
5265                            }
5266                        ));
5267                    }
5268                }
5269                JoinKind::Left => {
5270                    if join.use_outer_keyword {
5271                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5272                    } else if join.use_inner_keyword {
5273                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5274                    } else {
5275                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5276                    }
5277                }
5278                JoinKind::Right => {
5279                    if join.use_outer_keyword {
5280                        self.write_keyword(&format!(
5281                            "RIGHT OUTER{}{} JOIN",
5282                            hint_str, directed_str
5283                        ));
5284                    } else if join.use_inner_keyword {
5285                        self.write_keyword(&format!(
5286                            "RIGHT INNER{}{} JOIN",
5287                            hint_str, directed_str
5288                        ));
5289                    } else {
5290                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5291                    }
5292                }
5293                JoinKind::Full => {
5294                    if join.use_outer_keyword {
5295                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5296                    } else {
5297                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5298                    }
5299                }
5300                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5301                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5302                JoinKind::Natural => {
5303                    if join.use_inner_keyword {
5304                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5305                    } else {
5306                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5307                    }
5308                }
5309                JoinKind::NaturalLeft => {
5310                    if join.use_outer_keyword {
5311                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5312                    } else {
5313                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5314                    }
5315                }
5316                JoinKind::NaturalRight => {
5317                    if join.use_outer_keyword {
5318                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5319                    } else {
5320                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5321                    }
5322                }
5323                JoinKind::NaturalFull => {
5324                    if join.use_outer_keyword {
5325                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5326                    } else {
5327                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5328                    }
5329                }
5330                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5331                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5332                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5333                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5334                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5335                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5336                JoinKind::CrossApply => {
5337                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5338                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5339                        self.write_keyword("CROSS APPLY");
5340                    } else {
5341                        self.write_keyword("INNER JOIN LATERAL");
5342                    }
5343                }
5344                JoinKind::OuterApply => {
5345                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5346                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5347                        self.write_keyword("OUTER APPLY");
5348                    } else {
5349                        self.write_keyword("LEFT JOIN LATERAL");
5350                    }
5351                }
5352                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5353                JoinKind::AsOfLeft => {
5354                    if join.use_outer_keyword {
5355                        self.write_keyword("ASOF LEFT OUTER JOIN");
5356                    } else {
5357                        self.write_keyword("ASOF LEFT JOIN");
5358                    }
5359                }
5360                JoinKind::AsOfRight => {
5361                    if join.use_outer_keyword {
5362                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5363                    } else {
5364                        self.write_keyword("ASOF RIGHT JOIN");
5365                    }
5366                }
5367                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5368                JoinKind::LeftLateral => {
5369                    if join.use_outer_keyword {
5370                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5371                    } else {
5372                        self.write_keyword("LEFT LATERAL JOIN");
5373                    }
5374                }
5375                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5376                JoinKind::Implicit => {
5377                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5378                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5379                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5380                    use crate::dialects::DialectType;
5381                    let is_cj_dialect = matches!(
5382                        self.config.dialect,
5383                        Some(DialectType::BigQuery)
5384                            | Some(DialectType::Hive)
5385                            | Some(DialectType::Spark)
5386                            | Some(DialectType::Databricks)
5387                    );
5388                    let source_is_same = self.config.source_dialect.is_some()
5389                        && self.config.source_dialect == self.config.dialect;
5390                    let source_is_cj = matches!(
5391                        self.config.source_dialect,
5392                        Some(DialectType::BigQuery)
5393                            | Some(DialectType::Hive)
5394                            | Some(DialectType::Spark)
5395                            | Some(DialectType::Databricks)
5396                    );
5397                    if is_cj_dialect
5398                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5399                    {
5400                        self.write_keyword("CROSS JOIN");
5401                    } else {
5402                        // Implicit join uses comma: FROM a, b
5403                        // We already wrote a space before the match, so replace with comma
5404                        // by removing trailing space and writing ", "
5405                        self.output.truncate(self.output.trim_end().len());
5406                        self.write(",");
5407                    }
5408                }
5409                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5410                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5411                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5412            }
5413        }
5414
5415        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5416        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5417            self.write_space();
5418            match &join.this {
5419                Expression::Tuple(t) => {
5420                    for (i, item) in t.expressions.iter().enumerate() {
5421                        if i > 0 {
5422                            self.write(", ");
5423                        }
5424                        self.generate_expression(item)?;
5425                    }
5426                }
5427                other => {
5428                    self.generate_expression(other)?;
5429                }
5430            }
5431        } else {
5432            self.write_space();
5433            self.generate_expression(&join.this)?;
5434        }
5435
5436        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5437        if !join.deferred_condition {
5438            // Output 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                    self.generate_join_on_condition(on)?;
5455                    self.indent_level -= 1;
5456                } else {
5457                    self.write_space();
5458                    self.write_keyword("ON");
5459                    self.write_space();
5460                    self.generate_expression(on)?;
5461                }
5462            }
5463
5464            if !join.using.is_empty() {
5465                if self.config.pretty {
5466                    self.write_newline();
5467                    self.indent_level += 1;
5468                    self.write_indent();
5469                    self.write_keyword("USING");
5470                    self.write(" (");
5471                    for (i, col) in join.using.iter().enumerate() {
5472                        if i > 0 {
5473                            self.write(", ");
5474                        }
5475                        self.generate_identifier(col)?;
5476                    }
5477                    self.write(")");
5478                    self.indent_level -= 1;
5479                } else {
5480                    self.write_space();
5481                    self.write_keyword("USING");
5482                    self.write(" (");
5483                    for (i, col) in join.using.iter().enumerate() {
5484                        if i > 0 {
5485                            self.write(", ");
5486                        }
5487                        self.generate_identifier(col)?;
5488                    }
5489                    self.write(")");
5490                }
5491            }
5492        }
5493
5494        // Generate PIVOT/UNPIVOT expressions that follow this join
5495        for pivot in &join.pivots {
5496            self.write_space();
5497            self.generate_expression(pivot)?;
5498        }
5499
5500        Ok(())
5501    }
5502
5503    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5504    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5505        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5506        if let Some(match_cond) = &join.match_condition {
5507            self.write_space();
5508            self.write_keyword("MATCH_CONDITION");
5509            self.write(" (");
5510            self.generate_expression(match_cond)?;
5511            self.write(")");
5512        }
5513
5514        if let Some(on) = &join.on {
5515            if self.config.pretty {
5516                self.write_newline();
5517                self.indent_level += 1;
5518                self.write_indent();
5519                self.write_keyword("ON");
5520                self.write_space();
5521                // In pretty mode, split AND conditions onto separate lines
5522                self.generate_join_on_condition(on)?;
5523                self.indent_level -= 1;
5524            } else {
5525                self.write_space();
5526                self.write_keyword("ON");
5527                self.write_space();
5528                self.generate_expression(on)?;
5529            }
5530        }
5531
5532        if !join.using.is_empty() {
5533            if self.config.pretty {
5534                self.write_newline();
5535                self.indent_level += 1;
5536                self.write_indent();
5537                self.write_keyword("USING");
5538                self.write(" (");
5539                for (i, col) in join.using.iter().enumerate() {
5540                    if i > 0 {
5541                        self.write(", ");
5542                    }
5543                    self.generate_identifier(col)?;
5544                }
5545                self.write(")");
5546                self.indent_level -= 1;
5547            } else {
5548                self.write_space();
5549                self.write_keyword("USING");
5550                self.write(" (");
5551                for (i, col) in join.using.iter().enumerate() {
5552                    if i > 0 {
5553                        self.write(", ");
5554                    }
5555                    self.generate_identifier(col)?;
5556                }
5557                self.write(")");
5558            }
5559        }
5560
5561        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5562        for pivot in &join.pivots {
5563            self.write_space();
5564            self.generate_expression(pivot)?;
5565        }
5566
5567        Ok(())
5568    }
5569
5570    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5571    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5572        if let Expression::And(and_op) = expr {
5573            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5574                self.generate_expression(conditions[0])?;
5575                for condition in conditions.iter().skip(1) {
5576                    self.write_newline();
5577                    self.write_indent();
5578                    self.write_keyword("AND");
5579                    self.write_space();
5580                    self.generate_expression(condition)?;
5581                }
5582                return Ok(());
5583            }
5584        }
5585
5586        self.generate_expression(expr)
5587    }
5588
5589    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5590        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5591        self.write("(");
5592        self.generate_expression(&jt.left)?;
5593
5594        // Generate all joins
5595        for join in &jt.joins {
5596            self.generate_join(join)?;
5597        }
5598
5599        // Generate LATERAL VIEW clauses (Hive/Spark)
5600        for lv in &jt.lateral_views {
5601            self.generate_lateral_view(lv)?;
5602        }
5603
5604        self.write(")");
5605
5606        // Alias
5607        if let Some(alias) = &jt.alias {
5608            self.write_space();
5609            self.write_keyword("AS");
5610            self.write_space();
5611            self.generate_identifier(alias)?;
5612        }
5613
5614        Ok(())
5615    }
5616
5617    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5618        use crate::dialects::DialectType;
5619
5620        if self.config.pretty {
5621            self.write_newline();
5622            self.write_indent();
5623        } else {
5624            self.write_space();
5625        }
5626
5627        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5628        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5629        let use_lateral_join = matches!(
5630            self.config.dialect,
5631            Some(DialectType::PostgreSQL)
5632                | Some(DialectType::DuckDB)
5633                | Some(DialectType::Snowflake)
5634                | Some(DialectType::TSQL)
5635                | Some(DialectType::Presto)
5636                | Some(DialectType::Trino)
5637                | Some(DialectType::Athena)
5638        );
5639
5640        // Check if target dialect should use UNNEST instead of EXPLODE
5641        let use_unnest = matches!(
5642            self.config.dialect,
5643            Some(DialectType::DuckDB)
5644                | Some(DialectType::Presto)
5645                | Some(DialectType::Trino)
5646                | Some(DialectType::Athena)
5647        );
5648
5649        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5650        let (is_posexplode, func_args) = match &lv.this {
5651            Expression::Explode(uf) => {
5652                // Expression::Explode is the dedicated EXPLODE expression type
5653                (false, vec![uf.this.clone()])
5654            }
5655            Expression::Unnest(uf) => {
5656                let mut args = vec![uf.this.clone()];
5657                args.extend(uf.expressions.clone());
5658                (false, args)
5659            }
5660            Expression::Function(func) => {
5661                let name = func.name.to_uppercase();
5662                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5663                    (true, func.args.clone())
5664                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5665                    (false, func.args.clone())
5666                } else {
5667                    (false, vec![])
5668                }
5669            }
5670            _ => (false, vec![]),
5671        };
5672
5673        if use_lateral_join {
5674            // Convert to CROSS JOIN for PostgreSQL-like dialects
5675            if lv.outer {
5676                self.write_keyword("LEFT JOIN LATERAL");
5677            } else {
5678                self.write_keyword("CROSS JOIN");
5679            }
5680            self.write_space();
5681
5682            if use_unnest && !func_args.is_empty() {
5683                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5684                // For DuckDB, also convert ARRAY(y) -> [y]
5685                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5686                    // DuckDB: ARRAY(y) -> [y]
5687                    func_args
5688                        .iter()
5689                        .map(|a| {
5690                            if let Expression::Function(ref f) = a {
5691                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5692                                    return Expression::ArrayFunc(Box::new(
5693                                        crate::expressions::ArrayConstructor {
5694                                            expressions: f.args.clone(),
5695                                            bracket_notation: true,
5696                                            use_list_keyword: false,
5697                                        },
5698                                    ));
5699                                }
5700                            }
5701                            a.clone()
5702                        })
5703                        .collect::<Vec<_>>()
5704                } else if matches!(
5705                    self.config.dialect,
5706                    Some(DialectType::Presto)
5707                        | Some(DialectType::Trino)
5708                        | Some(DialectType::Athena)
5709                ) {
5710                    // Presto: ARRAY(y) -> ARRAY[y]
5711                    func_args
5712                        .iter()
5713                        .map(|a| {
5714                            if let Expression::Function(ref f) = a {
5715                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5716                                    return Expression::ArrayFunc(Box::new(
5717                                        crate::expressions::ArrayConstructor {
5718                                            expressions: f.args.clone(),
5719                                            bracket_notation: true,
5720                                            use_list_keyword: false,
5721                                        },
5722                                    ));
5723                                }
5724                            }
5725                            a.clone()
5726                        })
5727                        .collect::<Vec<_>>()
5728                } else {
5729                    func_args
5730                };
5731
5732                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5733                if is_posexplode {
5734                    self.write_keyword("LATERAL");
5735                    self.write(" (");
5736                    self.write_keyword("SELECT");
5737                    self.write_space();
5738
5739                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5740                    // column_aliases[0] is the position column, rest are data columns
5741                    let pos_alias = if !lv.column_aliases.is_empty() {
5742                        lv.column_aliases[0].clone()
5743                    } else {
5744                        Identifier::new("pos")
5745                    };
5746                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5747                        lv.column_aliases[1..].to_vec()
5748                    } else {
5749                        vec![Identifier::new("col")]
5750                    };
5751
5752                    // pos - 1 AS pos
5753                    self.generate_identifier(&pos_alias)?;
5754                    self.write(" - 1");
5755                    self.write_space();
5756                    self.write_keyword("AS");
5757                    self.write_space();
5758                    self.generate_identifier(&pos_alias)?;
5759
5760                    // , col [, key, value ...]
5761                    for data_col in &data_aliases {
5762                        self.write(", ");
5763                        self.generate_identifier(data_col)?;
5764                    }
5765
5766                    self.write_space();
5767                    self.write_keyword("FROM");
5768                    self.write_space();
5769                    self.write_keyword("UNNEST");
5770                    self.write("(");
5771                    for (i, arg) in unnest_args.iter().enumerate() {
5772                        if i > 0 {
5773                            self.write(", ");
5774                        }
5775                        self.generate_expression(arg)?;
5776                    }
5777                    self.write(")");
5778                    self.write_space();
5779                    self.write_keyword("WITH ORDINALITY");
5780                    self.write_space();
5781                    self.write_keyword("AS");
5782                    self.write_space();
5783
5784                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5785                    let table_alias_ident = lv
5786                        .table_alias
5787                        .clone()
5788                        .unwrap_or_else(|| Identifier::new("t"));
5789                    self.generate_identifier(&table_alias_ident)?;
5790                    self.write("(");
5791                    for (i, data_col) in data_aliases.iter().enumerate() {
5792                        if i > 0 {
5793                            self.write(", ");
5794                        }
5795                        self.generate_identifier(data_col)?;
5796                    }
5797                    self.write(", ");
5798                    self.generate_identifier(&pos_alias)?;
5799                    self.write("))");
5800                } else {
5801                    self.write_keyword("UNNEST");
5802                    self.write("(");
5803                    for (i, arg) in unnest_args.iter().enumerate() {
5804                        if i > 0 {
5805                            self.write(", ");
5806                        }
5807                        self.generate_expression(arg)?;
5808                    }
5809                    self.write(")");
5810
5811                    // Add table and column aliases for non-POSEXPLODE
5812                    if let Some(alias) = &lv.table_alias {
5813                        self.write_space();
5814                        self.write_keyword("AS");
5815                        self.write_space();
5816                        self.generate_identifier(alias)?;
5817                        if !lv.column_aliases.is_empty() {
5818                            self.write("(");
5819                            for (i, col) in lv.column_aliases.iter().enumerate() {
5820                                if i > 0 {
5821                                    self.write(", ");
5822                                }
5823                                self.generate_identifier(col)?;
5824                            }
5825                            self.write(")");
5826                        }
5827                    } else if !lv.column_aliases.is_empty() {
5828                        self.write_space();
5829                        self.write_keyword("AS");
5830                        self.write(" t(");
5831                        for (i, col) in lv.column_aliases.iter().enumerate() {
5832                            if i > 0 {
5833                                self.write(", ");
5834                            }
5835                            self.generate_identifier(col)?;
5836                        }
5837                        self.write(")");
5838                    }
5839                }
5840            } else {
5841                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5842                if !lv.outer {
5843                    self.write_keyword("LATERAL");
5844                    self.write_space();
5845                }
5846                self.generate_expression(&lv.this)?;
5847
5848                // Add table and column aliases
5849                if let Some(alias) = &lv.table_alias {
5850                    self.write_space();
5851                    self.write_keyword("AS");
5852                    self.write_space();
5853                    self.generate_identifier(alias)?;
5854                    if !lv.column_aliases.is_empty() {
5855                        self.write("(");
5856                        for (i, col) in lv.column_aliases.iter().enumerate() {
5857                            if i > 0 {
5858                                self.write(", ");
5859                            }
5860                            self.generate_identifier(col)?;
5861                        }
5862                        self.write(")");
5863                    }
5864                } else if !lv.column_aliases.is_empty() {
5865                    self.write_space();
5866                    self.write_keyword("AS");
5867                    self.write(" t(");
5868                    for (i, col) in lv.column_aliases.iter().enumerate() {
5869                        if i > 0 {
5870                            self.write(", ");
5871                        }
5872                        self.generate_identifier(col)?;
5873                    }
5874                    self.write(")");
5875                }
5876            }
5877
5878            // For LEFT JOIN LATERAL, need ON TRUE
5879            if lv.outer {
5880                self.write_space();
5881                self.write_keyword("ON TRUE");
5882            }
5883        } else {
5884            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5885            self.write_keyword("LATERAL VIEW");
5886            if lv.outer {
5887                self.write_space();
5888                self.write_keyword("OUTER");
5889            }
5890            if self.config.pretty {
5891                self.write_newline();
5892                self.write_indent();
5893            } else {
5894                self.write_space();
5895            }
5896            self.generate_expression(&lv.this)?;
5897
5898            // Table alias
5899            if let Some(alias) = &lv.table_alias {
5900                self.write_space();
5901                self.generate_identifier(alias)?;
5902            }
5903
5904            // Column aliases
5905            if !lv.column_aliases.is_empty() {
5906                self.write_space();
5907                self.write_keyword("AS");
5908                self.write_space();
5909                for (i, col) in lv.column_aliases.iter().enumerate() {
5910                    if i > 0 {
5911                        self.write(", ");
5912                    }
5913                    self.generate_identifier(col)?;
5914                }
5915            }
5916        }
5917
5918        Ok(())
5919    }
5920
5921    fn generate_union(&mut self, union: &Union) -> Result<()> {
5922        // WITH clause
5923        if let Some(with) = &union.with {
5924            self.generate_with(with)?;
5925            self.write_space();
5926        }
5927        self.generate_expression(&union.left)?;
5928        if self.config.pretty {
5929            self.write_newline();
5930            self.write_indent();
5931        } else {
5932            self.write_space();
5933        }
5934
5935        // BigQuery set operation modifiers: [side] [kind] UNION
5936        if let Some(side) = &union.side {
5937            self.write_keyword(side);
5938            self.write_space();
5939        }
5940        if let Some(kind) = &union.kind {
5941            self.write_keyword(kind);
5942            self.write_space();
5943        }
5944
5945        self.write_keyword("UNION");
5946        if union.all {
5947            self.write_space();
5948            self.write_keyword("ALL");
5949        } else if union.distinct {
5950            self.write_space();
5951            self.write_keyword("DISTINCT");
5952        }
5953
5954        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5955        // DuckDB: BY NAME
5956        if union.corresponding || union.by_name {
5957            self.write_space();
5958            self.write_keyword("BY NAME");
5959        }
5960        if !union.on_columns.is_empty() {
5961            self.write_space();
5962            self.write_keyword("ON");
5963            self.write(" (");
5964            for (i, col) in union.on_columns.iter().enumerate() {
5965                if i > 0 {
5966                    self.write(", ");
5967                }
5968                self.generate_expression(col)?;
5969            }
5970            self.write(")");
5971        }
5972
5973        if self.config.pretty {
5974            self.write_newline();
5975            self.write_indent();
5976        } else {
5977            self.write_space();
5978        }
5979        self.generate_expression(&union.right)?;
5980        // ORDER BY, LIMIT, OFFSET for the set operation
5981        if let Some(order_by) = &union.order_by {
5982            if self.config.pretty {
5983                self.write_newline();
5984            } else {
5985                self.write_space();
5986            }
5987            self.write_keyword("ORDER BY");
5988            self.write_space();
5989            for (i, ordered) in order_by.expressions.iter().enumerate() {
5990                if i > 0 {
5991                    self.write(", ");
5992                }
5993                self.generate_ordered(ordered)?;
5994            }
5995        }
5996        if let Some(limit) = &union.limit {
5997            if self.config.pretty {
5998                self.write_newline();
5999            } else {
6000                self.write_space();
6001            }
6002            self.write_keyword("LIMIT");
6003            self.write_space();
6004            self.generate_expression(limit)?;
6005        }
6006        if let Some(offset) = &union.offset {
6007            if self.config.pretty {
6008                self.write_newline();
6009            } else {
6010                self.write_space();
6011            }
6012            self.write_keyword("OFFSET");
6013            self.write_space();
6014            self.generate_expression(offset)?;
6015        }
6016        // DISTRIBUTE BY (Hive/Spark)
6017        if let Some(distribute_by) = &union.distribute_by {
6018            self.write_space();
6019            self.write_keyword("DISTRIBUTE BY");
6020            self.write_space();
6021            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6022                if i > 0 {
6023                    self.write(", ");
6024                }
6025                self.generate_expression(expr)?;
6026            }
6027        }
6028        // SORT BY (Hive/Spark)
6029        if let Some(sort_by) = &union.sort_by {
6030            self.write_space();
6031            self.write_keyword("SORT BY");
6032            self.write_space();
6033            for (i, ord) in sort_by.expressions.iter().enumerate() {
6034                if i > 0 {
6035                    self.write(", ");
6036                }
6037                self.generate_ordered(ord)?;
6038            }
6039        }
6040        // CLUSTER BY (Hive/Spark)
6041        if let Some(cluster_by) = &union.cluster_by {
6042            self.write_space();
6043            self.write_keyword("CLUSTER BY");
6044            self.write_space();
6045            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6046                if i > 0 {
6047                    self.write(", ");
6048                }
6049                self.generate_ordered(ord)?;
6050            }
6051        }
6052        Ok(())
6053    }
6054
6055    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
6056        // WITH clause
6057        if let Some(with) = &intersect.with {
6058            self.generate_with(with)?;
6059            self.write_space();
6060        }
6061        self.generate_expression(&intersect.left)?;
6062        if self.config.pretty {
6063            self.write_newline();
6064            self.write_indent();
6065        } else {
6066            self.write_space();
6067        }
6068
6069        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6070        if let Some(side) = &intersect.side {
6071            self.write_keyword(side);
6072            self.write_space();
6073        }
6074        if let Some(kind) = &intersect.kind {
6075            self.write_keyword(kind);
6076            self.write_space();
6077        }
6078
6079        self.write_keyword("INTERSECT");
6080        if intersect.all {
6081            self.write_space();
6082            self.write_keyword("ALL");
6083        } else if intersect.distinct {
6084            self.write_space();
6085            self.write_keyword("DISTINCT");
6086        }
6087
6088        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6089        // DuckDB: BY NAME
6090        if intersect.corresponding || intersect.by_name {
6091            self.write_space();
6092            self.write_keyword("BY NAME");
6093        }
6094        if !intersect.on_columns.is_empty() {
6095            self.write_space();
6096            self.write_keyword("ON");
6097            self.write(" (");
6098            for (i, col) in intersect.on_columns.iter().enumerate() {
6099                if i > 0 {
6100                    self.write(", ");
6101                }
6102                self.generate_expression(col)?;
6103            }
6104            self.write(")");
6105        }
6106
6107        if self.config.pretty {
6108            self.write_newline();
6109            self.write_indent();
6110        } else {
6111            self.write_space();
6112        }
6113        self.generate_expression(&intersect.right)?;
6114        // ORDER BY, LIMIT, OFFSET for the set operation
6115        if let Some(order_by) = &intersect.order_by {
6116            if self.config.pretty {
6117                self.write_newline();
6118            } else {
6119                self.write_space();
6120            }
6121            self.write_keyword("ORDER BY");
6122            self.write_space();
6123            for (i, ordered) in order_by.expressions.iter().enumerate() {
6124                if i > 0 {
6125                    self.write(", ");
6126                }
6127                self.generate_ordered(ordered)?;
6128            }
6129        }
6130        if let Some(limit) = &intersect.limit {
6131            if self.config.pretty {
6132                self.write_newline();
6133            } else {
6134                self.write_space();
6135            }
6136            self.write_keyword("LIMIT");
6137            self.write_space();
6138            self.generate_expression(limit)?;
6139        }
6140        if let Some(offset) = &intersect.offset {
6141            if self.config.pretty {
6142                self.write_newline();
6143            } else {
6144                self.write_space();
6145            }
6146            self.write_keyword("OFFSET");
6147            self.write_space();
6148            self.generate_expression(offset)?;
6149        }
6150        // DISTRIBUTE BY (Hive/Spark)
6151        if let Some(distribute_by) = &intersect.distribute_by {
6152            self.write_space();
6153            self.write_keyword("DISTRIBUTE BY");
6154            self.write_space();
6155            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6156                if i > 0 {
6157                    self.write(", ");
6158                }
6159                self.generate_expression(expr)?;
6160            }
6161        }
6162        // SORT BY (Hive/Spark)
6163        if let Some(sort_by) = &intersect.sort_by {
6164            self.write_space();
6165            self.write_keyword("SORT BY");
6166            self.write_space();
6167            for (i, ord) in sort_by.expressions.iter().enumerate() {
6168                if i > 0 {
6169                    self.write(", ");
6170                }
6171                self.generate_ordered(ord)?;
6172            }
6173        }
6174        // CLUSTER BY (Hive/Spark)
6175        if let Some(cluster_by) = &intersect.cluster_by {
6176            self.write_space();
6177            self.write_keyword("CLUSTER BY");
6178            self.write_space();
6179            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6180                if i > 0 {
6181                    self.write(", ");
6182                }
6183                self.generate_ordered(ord)?;
6184            }
6185        }
6186        Ok(())
6187    }
6188
6189    fn generate_except(&mut self, except: &Except) -> Result<()> {
6190        use crate::dialects::DialectType;
6191
6192        // WITH clause
6193        if let Some(with) = &except.with {
6194            self.generate_with(with)?;
6195            self.write_space();
6196        }
6197
6198        self.generate_expression(&except.left)?;
6199        if self.config.pretty {
6200            self.write_newline();
6201            self.write_indent();
6202        } else {
6203            self.write_space();
6204        }
6205
6206        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6207        if let Some(side) = &except.side {
6208            self.write_keyword(side);
6209            self.write_space();
6210        }
6211        if let Some(kind) = &except.kind {
6212            self.write_keyword(kind);
6213            self.write_space();
6214        }
6215
6216        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6217        match self.config.dialect {
6218            Some(DialectType::Oracle) if !except.all => {
6219                self.write_keyword("MINUS");
6220            }
6221            Some(DialectType::ClickHouse) => {
6222                // ClickHouse: drop ALL from EXCEPT ALL
6223                self.write_keyword("EXCEPT");
6224                if except.distinct {
6225                    self.write_space();
6226                    self.write_keyword("DISTINCT");
6227                }
6228            }
6229            Some(DialectType::BigQuery) => {
6230                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6231                self.write_keyword("EXCEPT");
6232                if except.all {
6233                    self.write_space();
6234                    self.write_keyword("ALL");
6235                } else {
6236                    self.write_space();
6237                    self.write_keyword("DISTINCT");
6238                }
6239            }
6240            _ => {
6241                self.write_keyword("EXCEPT");
6242                if except.all {
6243                    self.write_space();
6244                    self.write_keyword("ALL");
6245                } else if except.distinct {
6246                    self.write_space();
6247                    self.write_keyword("DISTINCT");
6248                }
6249            }
6250        }
6251
6252        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6253        // DuckDB: BY NAME
6254        if except.corresponding || except.by_name {
6255            self.write_space();
6256            self.write_keyword("BY NAME");
6257        }
6258        if !except.on_columns.is_empty() {
6259            self.write_space();
6260            self.write_keyword("ON");
6261            self.write(" (");
6262            for (i, col) in except.on_columns.iter().enumerate() {
6263                if i > 0 {
6264                    self.write(", ");
6265                }
6266                self.generate_expression(col)?;
6267            }
6268            self.write(")");
6269        }
6270
6271        if self.config.pretty {
6272            self.write_newline();
6273            self.write_indent();
6274        } else {
6275            self.write_space();
6276        }
6277        self.generate_expression(&except.right)?;
6278        // ORDER BY, LIMIT, OFFSET for the set operation
6279        if let Some(order_by) = &except.order_by {
6280            if self.config.pretty {
6281                self.write_newline();
6282            } else {
6283                self.write_space();
6284            }
6285            self.write_keyword("ORDER BY");
6286            self.write_space();
6287            for (i, ordered) in order_by.expressions.iter().enumerate() {
6288                if i > 0 {
6289                    self.write(", ");
6290                }
6291                self.generate_ordered(ordered)?;
6292            }
6293        }
6294        if let Some(limit) = &except.limit {
6295            if self.config.pretty {
6296                self.write_newline();
6297            } else {
6298                self.write_space();
6299            }
6300            self.write_keyword("LIMIT");
6301            self.write_space();
6302            self.generate_expression(limit)?;
6303        }
6304        if let Some(offset) = &except.offset {
6305            if self.config.pretty {
6306                self.write_newline();
6307            } else {
6308                self.write_space();
6309            }
6310            self.write_keyword("OFFSET");
6311            self.write_space();
6312            self.generate_expression(offset)?;
6313        }
6314        // DISTRIBUTE BY (Hive/Spark)
6315        if let Some(distribute_by) = &except.distribute_by {
6316            self.write_space();
6317            self.write_keyword("DISTRIBUTE BY");
6318            self.write_space();
6319            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6320                if i > 0 {
6321                    self.write(", ");
6322                }
6323                self.generate_expression(expr)?;
6324            }
6325        }
6326        // SORT BY (Hive/Spark)
6327        if let Some(sort_by) = &except.sort_by {
6328            self.write_space();
6329            self.write_keyword("SORT BY");
6330            self.write_space();
6331            for (i, ord) in sort_by.expressions.iter().enumerate() {
6332                if i > 0 {
6333                    self.write(", ");
6334                }
6335                self.generate_ordered(ord)?;
6336            }
6337        }
6338        // CLUSTER BY (Hive/Spark)
6339        if let Some(cluster_by) = &except.cluster_by {
6340            self.write_space();
6341            self.write_keyword("CLUSTER BY");
6342            self.write_space();
6343            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6344                if i > 0 {
6345                    self.write(", ");
6346                }
6347                self.generate_ordered(ord)?;
6348            }
6349        }
6350        Ok(())
6351    }
6352
6353    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6354        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6355        let prepend_query_cte = if insert.with.is_none() {
6356            use crate::dialects::DialectType;
6357            let should_prepend = matches!(
6358                self.config.dialect,
6359                Some(DialectType::TSQL)
6360                    | Some(DialectType::Fabric)
6361                    | Some(DialectType::Spark)
6362                    | Some(DialectType::Databricks)
6363                    | Some(DialectType::Hive)
6364            );
6365            if should_prepend {
6366                if let Some(Expression::Select(select)) = &insert.query {
6367                    select.with.clone()
6368                } else {
6369                    None
6370                }
6371            } else {
6372                None
6373            }
6374        } else {
6375            None
6376        };
6377
6378        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6379        if let Some(with) = &insert.with {
6380            self.generate_with(with)?;
6381            self.write_space();
6382        } else if let Some(with) = &prepend_query_cte {
6383            self.generate_with(with)?;
6384            self.write_space();
6385        }
6386
6387        // Output leading comments before INSERT
6388        for comment in &insert.leading_comments {
6389            self.write_formatted_comment(comment);
6390            self.write(" ");
6391        }
6392
6393        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6394        if let Some(dir) = &insert.directory {
6395            self.write_keyword("INSERT OVERWRITE");
6396            if dir.local {
6397                self.write_space();
6398                self.write_keyword("LOCAL");
6399            }
6400            self.write_space();
6401            self.write_keyword("DIRECTORY");
6402            self.write_space();
6403            self.write("'");
6404            self.write(&dir.path);
6405            self.write("'");
6406
6407            // ROW FORMAT clause
6408            if let Some(row_format) = &dir.row_format {
6409                self.write_space();
6410                self.write_keyword("ROW FORMAT");
6411                if row_format.delimited {
6412                    self.write_space();
6413                    self.write_keyword("DELIMITED");
6414                }
6415                if let Some(val) = &row_format.fields_terminated_by {
6416                    self.write_space();
6417                    self.write_keyword("FIELDS TERMINATED BY");
6418                    self.write_space();
6419                    self.write("'");
6420                    self.write(val);
6421                    self.write("'");
6422                }
6423                if let Some(val) = &row_format.collection_items_terminated_by {
6424                    self.write_space();
6425                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6426                    self.write_space();
6427                    self.write("'");
6428                    self.write(val);
6429                    self.write("'");
6430                }
6431                if let Some(val) = &row_format.map_keys_terminated_by {
6432                    self.write_space();
6433                    self.write_keyword("MAP KEYS TERMINATED BY");
6434                    self.write_space();
6435                    self.write("'");
6436                    self.write(val);
6437                    self.write("'");
6438                }
6439                if let Some(val) = &row_format.lines_terminated_by {
6440                    self.write_space();
6441                    self.write_keyword("LINES TERMINATED BY");
6442                    self.write_space();
6443                    self.write("'");
6444                    self.write(val);
6445                    self.write("'");
6446                }
6447                if let Some(val) = &row_format.null_defined_as {
6448                    self.write_space();
6449                    self.write_keyword("NULL DEFINED AS");
6450                    self.write_space();
6451                    self.write("'");
6452                    self.write(val);
6453                    self.write("'");
6454                }
6455            }
6456
6457            // STORED AS clause
6458            if let Some(format) = &dir.stored_as {
6459                self.write_space();
6460                self.write_keyword("STORED AS");
6461                self.write_space();
6462                self.write_keyword(format);
6463            }
6464
6465            // Query (SELECT statement)
6466            if let Some(query) = &insert.query {
6467                self.write_space();
6468                self.generate_expression(query)?;
6469            }
6470
6471            return Ok(());
6472        }
6473
6474        if insert.is_replace {
6475            // MySQL/SQLite REPLACE INTO statement
6476            self.write_keyword("REPLACE INTO");
6477        } else if insert.overwrite {
6478            // Use dialect-specific INSERT OVERWRITE format
6479            self.write_keyword("INSERT");
6480            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6481            if let Some(ref hint) = insert.hint {
6482                self.generate_hint(hint)?;
6483            }
6484            self.write(&self.config.insert_overwrite.to_uppercase());
6485        } else if let Some(ref action) = insert.conflict_action {
6486            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6487            self.write_keyword("INSERT OR");
6488            self.write_space();
6489            self.write_keyword(action);
6490            self.write_space();
6491            self.write_keyword("INTO");
6492        } else if insert.ignore {
6493            // MySQL INSERT IGNORE syntax
6494            self.write_keyword("INSERT IGNORE INTO");
6495        } else {
6496            self.write_keyword("INSERT");
6497            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6498            if let Some(ref hint) = insert.hint {
6499                self.generate_hint(hint)?;
6500            }
6501            self.write_space();
6502            self.write_keyword("INTO");
6503        }
6504        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6505        if let Some(ref func) = insert.function_target {
6506            self.write_space();
6507            self.write_keyword("FUNCTION");
6508            self.write_space();
6509            self.generate_expression(func)?;
6510        } else {
6511            self.write_space();
6512            self.generate_table(&insert.table)?;
6513        }
6514
6515        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6516        if let Some(ref alias) = insert.alias {
6517            self.write_space();
6518            if insert.alias_explicit_as {
6519                self.write_keyword("AS");
6520                self.write_space();
6521            }
6522            self.generate_identifier(alias)?;
6523        }
6524
6525        // IF EXISTS clause (Hive)
6526        if insert.if_exists {
6527            self.write_space();
6528            self.write_keyword("IF EXISTS");
6529        }
6530
6531        // REPLACE WHERE clause (Databricks)
6532        if let Some(ref replace_where) = insert.replace_where {
6533            if self.config.pretty {
6534                self.write_newline();
6535                self.write_indent();
6536            } else {
6537                self.write_space();
6538            }
6539            self.write_keyword("REPLACE WHERE");
6540            self.write_space();
6541            self.generate_expression(replace_where)?;
6542        }
6543
6544        // Generate PARTITION clause if present
6545        if !insert.partition.is_empty() {
6546            self.write_space();
6547            self.write_keyword("PARTITION");
6548            self.write("(");
6549            for (i, (col, val)) in insert.partition.iter().enumerate() {
6550                if i > 0 {
6551                    self.write(", ");
6552                }
6553                self.generate_identifier(col)?;
6554                if let Some(v) = val {
6555                    self.write(" = ");
6556                    self.generate_expression(v)?;
6557                }
6558            }
6559            self.write(")");
6560        }
6561
6562        // ClickHouse: PARTITION BY expr
6563        if let Some(ref partition_by) = insert.partition_by {
6564            self.write_space();
6565            self.write_keyword("PARTITION BY");
6566            self.write_space();
6567            self.generate_expression(partition_by)?;
6568        }
6569
6570        // ClickHouse: SETTINGS key = val, ...
6571        if !insert.settings.is_empty() {
6572            self.write_space();
6573            self.write_keyword("SETTINGS");
6574            self.write_space();
6575            for (i, setting) in insert.settings.iter().enumerate() {
6576                if i > 0 {
6577                    self.write(", ");
6578                }
6579                self.generate_expression(setting)?;
6580            }
6581        }
6582
6583        if !insert.columns.is_empty() {
6584            if insert.alias.is_some() && insert.alias_explicit_as {
6585                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6586                self.write("(");
6587            } else {
6588                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6589                self.write(" (");
6590            }
6591            for (i, col) in insert.columns.iter().enumerate() {
6592                if i > 0 {
6593                    self.write(", ");
6594                }
6595                self.generate_identifier(col)?;
6596            }
6597            self.write(")");
6598        }
6599
6600        // OUTPUT clause (TSQL)
6601        if let Some(ref output) = insert.output {
6602            self.generate_output_clause(output)?;
6603        }
6604
6605        // BY NAME modifier (DuckDB)
6606        if insert.by_name {
6607            self.write_space();
6608            self.write_keyword("BY NAME");
6609        }
6610
6611        if insert.default_values {
6612            self.write_space();
6613            self.write_keyword("DEFAULT VALUES");
6614        } else if let Some(query) = &insert.query {
6615            if self.config.pretty {
6616                self.write_newline();
6617            } else {
6618                self.write_space();
6619            }
6620            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6621            if prepend_query_cte.is_some() {
6622                if let Expression::Select(select) = query {
6623                    let mut select_no_with = select.clone();
6624                    select_no_with.with = None;
6625                    self.generate_select(&select_no_with)?;
6626                } else {
6627                    self.generate_expression(query)?;
6628                }
6629            } else {
6630                self.generate_expression(query)?;
6631            }
6632        } else if !insert.values.is_empty() {
6633            if self.config.pretty {
6634                // Pretty printing: VALUES on new line, each tuple indented
6635                self.write_newline();
6636                self.write_keyword("VALUES");
6637                self.write_newline();
6638                self.indent_level += 1;
6639                for (i, row) in insert.values.iter().enumerate() {
6640                    if i > 0 {
6641                        self.write(",");
6642                        self.write_newline();
6643                    }
6644                    self.write_indent();
6645                    self.write("(");
6646                    for (j, val) in row.iter().enumerate() {
6647                        if j > 0 {
6648                            self.write(", ");
6649                        }
6650                        self.generate_expression(val)?;
6651                    }
6652                    self.write(")");
6653                }
6654                self.indent_level -= 1;
6655            } else {
6656                // Non-pretty: single line
6657                self.write_space();
6658                self.write_keyword("VALUES");
6659                for (i, row) in insert.values.iter().enumerate() {
6660                    if i > 0 {
6661                        self.write(",");
6662                    }
6663                    self.write(" (");
6664                    for (j, val) in row.iter().enumerate() {
6665                        if j > 0 {
6666                            self.write(", ");
6667                        }
6668                        self.generate_expression(val)?;
6669                    }
6670                    self.write(")");
6671                }
6672            }
6673        }
6674
6675        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6676        if let Some(ref source) = insert.source {
6677            self.write_space();
6678            self.write_keyword("TABLE");
6679            self.write_space();
6680            self.generate_expression(source)?;
6681        }
6682
6683        // Source alias (MySQL: VALUES (...) AS new_data)
6684        if let Some(alias) = &insert.source_alias {
6685            self.write_space();
6686            self.write_keyword("AS");
6687            self.write_space();
6688            self.generate_identifier(alias)?;
6689        }
6690
6691        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6692        if let Some(on_conflict) = &insert.on_conflict {
6693            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6694                self.write_space();
6695                self.generate_expression(on_conflict)?;
6696            }
6697        }
6698
6699        // RETURNING clause
6700        if !insert.returning.is_empty() {
6701            self.write_space();
6702            self.write_keyword("RETURNING");
6703            self.write_space();
6704            for (i, expr) in insert.returning.iter().enumerate() {
6705                if i > 0 {
6706                    self.write(", ");
6707                }
6708                self.generate_expression(expr)?;
6709            }
6710        }
6711
6712        Ok(())
6713    }
6714
6715    fn generate_update(&mut self, update: &Update) -> Result<()> {
6716        // Output leading comments before UPDATE
6717        for comment in &update.leading_comments {
6718            self.write_formatted_comment(comment);
6719            self.write(" ");
6720        }
6721
6722        // WITH clause (CTEs)
6723        if let Some(ref with) = update.with {
6724            self.generate_with(with)?;
6725            self.write_space();
6726        }
6727
6728        self.write_keyword("UPDATE");
6729        self.write_space();
6730        self.generate_table(&update.table)?;
6731
6732        let mysql_like_update_from = matches!(
6733            self.config.dialect,
6734            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6735        ) && update.from_clause.is_some();
6736
6737        let mut set_pairs = update.set.clone();
6738
6739        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6740        let mut pre_set_joins = update.table_joins.clone();
6741        if mysql_like_update_from {
6742            let target_name = update
6743                .table
6744                .alias
6745                .as_ref()
6746                .map(|a| a.name.clone())
6747                .unwrap_or_else(|| update.table.name.name.clone());
6748
6749            for (col, _) in &mut set_pairs {
6750                if !col.name.contains('.') {
6751                    col.name = format!("{}.{}", target_name, col.name);
6752                }
6753            }
6754
6755            if let Some(from_clause) = &update.from_clause {
6756                for table_expr in &from_clause.expressions {
6757                    pre_set_joins.push(crate::expressions::Join {
6758                        this: table_expr.clone(),
6759                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6760                            value: true,
6761                        })),
6762                        using: Vec::new(),
6763                        kind: crate::expressions::JoinKind::Inner,
6764                        use_inner_keyword: false,
6765                        use_outer_keyword: false,
6766                        deferred_condition: false,
6767                        join_hint: None,
6768                        match_condition: None,
6769                        pivots: Vec::new(),
6770                        comments: Vec::new(),
6771                        nesting_group: 0,
6772                        directed: false,
6773                    });
6774                }
6775            }
6776            for join in &update.from_joins {
6777                let mut join = join.clone();
6778                if join.on.is_none() && join.using.is_empty() {
6779                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6780                        value: true,
6781                    }));
6782                }
6783                pre_set_joins.push(join);
6784            }
6785        }
6786
6787        // Extra tables for multi-table UPDATE (MySQL syntax)
6788        for extra_table in &update.extra_tables {
6789            self.write(", ");
6790            self.generate_table(extra_table)?;
6791        }
6792
6793        // JOINs attached to the table list (MySQL multi-table syntax)
6794        for join in &pre_set_joins {
6795            // generate_join already adds a leading space
6796            self.generate_join(join)?;
6797        }
6798
6799        // Teradata: FROM clause comes before SET
6800        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6801        if teradata_from_before_set && !mysql_like_update_from {
6802            if let Some(ref from_clause) = update.from_clause {
6803                self.write_space();
6804                self.write_keyword("FROM");
6805                self.write_space();
6806                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6807                    if i > 0 {
6808                        self.write(", ");
6809                    }
6810                    self.generate_expression(table_expr)?;
6811                }
6812            }
6813            for join in &update.from_joins {
6814                self.generate_join(join)?;
6815            }
6816        }
6817
6818        self.write_space();
6819        self.write_keyword("SET");
6820        self.write_space();
6821
6822        for (i, (col, val)) in set_pairs.iter().enumerate() {
6823            if i > 0 {
6824                self.write(", ");
6825            }
6826            self.generate_identifier(col)?;
6827            self.write(" = ");
6828            self.generate_expression(val)?;
6829        }
6830
6831        // OUTPUT clause (TSQL)
6832        if let Some(ref output) = update.output {
6833            self.generate_output_clause(output)?;
6834        }
6835
6836        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6837        if !mysql_like_update_from && !teradata_from_before_set {
6838            if let Some(ref from_clause) = update.from_clause {
6839                self.write_space();
6840                self.write_keyword("FROM");
6841                self.write_space();
6842                // Generate each table in the FROM clause
6843                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6844                    if i > 0 {
6845                        self.write(", ");
6846                    }
6847                    self.generate_expression(table_expr)?;
6848                }
6849            }
6850        }
6851
6852        if !mysql_like_update_from && !teradata_from_before_set {
6853            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6854            for join in &update.from_joins {
6855                self.generate_join(join)?;
6856            }
6857        }
6858
6859        if let Some(where_clause) = &update.where_clause {
6860            self.write_space();
6861            self.write_keyword("WHERE");
6862            self.write_space();
6863            self.generate_expression(&where_clause.this)?;
6864        }
6865
6866        // RETURNING clause
6867        if !update.returning.is_empty() {
6868            self.write_space();
6869            self.write_keyword("RETURNING");
6870            self.write_space();
6871            for (i, expr) in update.returning.iter().enumerate() {
6872                if i > 0 {
6873                    self.write(", ");
6874                }
6875                self.generate_expression(expr)?;
6876            }
6877        }
6878
6879        // ORDER BY clause (MySQL)
6880        if let Some(ref order_by) = update.order_by {
6881            self.write_space();
6882            self.generate_order_by(order_by)?;
6883        }
6884
6885        // LIMIT clause (MySQL)
6886        if let Some(ref limit) = update.limit {
6887            self.write_space();
6888            self.write_keyword("LIMIT");
6889            self.write_space();
6890            self.generate_expression(limit)?;
6891        }
6892
6893        Ok(())
6894    }
6895
6896    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6897        // Output WITH clause if present
6898        if let Some(with) = &delete.with {
6899            self.generate_with(with)?;
6900            self.write_space();
6901        }
6902
6903        // Output leading comments before DELETE
6904        for comment in &delete.leading_comments {
6905            self.write_formatted_comment(comment);
6906            self.write(" ");
6907        }
6908
6909        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6910        if !delete.tables.is_empty() && !delete.tables_from_using {
6911            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6912            self.write_keyword("DELETE");
6913            self.write_space();
6914            for (i, tbl) in delete.tables.iter().enumerate() {
6915                if i > 0 {
6916                    self.write(", ");
6917                }
6918                self.generate_table(tbl)?;
6919            }
6920            // TSQL: OUTPUT clause between target table and FROM
6921            if let Some(ref output) = delete.output {
6922                self.generate_output_clause(output)?;
6923            }
6924            self.write_space();
6925            self.write_keyword("FROM");
6926            self.write_space();
6927            self.generate_table(&delete.table)?;
6928        } else if !delete.tables.is_empty() && delete.tables_from_using {
6929            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6930            self.write_keyword("DELETE FROM");
6931            self.write_space();
6932            for (i, tbl) in delete.tables.iter().enumerate() {
6933                if i > 0 {
6934                    self.write(", ");
6935                }
6936                self.generate_table(tbl)?;
6937            }
6938        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6939            // BigQuery-style DELETE without FROM keyword
6940            self.write_keyword("DELETE");
6941            self.write_space();
6942            self.generate_table(&delete.table)?;
6943        } else {
6944            self.write_keyword("DELETE FROM");
6945            self.write_space();
6946            self.generate_table(&delete.table)?;
6947        }
6948
6949        // ClickHouse: ON CLUSTER clause
6950        if let Some(ref on_cluster) = delete.on_cluster {
6951            self.write_space();
6952            self.generate_on_cluster(on_cluster)?;
6953        }
6954
6955        // FORCE INDEX hint (MySQL)
6956        if let Some(ref idx) = delete.force_index {
6957            self.write_space();
6958            self.write_keyword("FORCE INDEX");
6959            self.write(" (");
6960            self.write(idx);
6961            self.write(")");
6962        }
6963
6964        // Optional alias
6965        if let Some(ref alias) = delete.alias {
6966            self.write_space();
6967            if delete.alias_explicit_as
6968                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6969            {
6970                self.write_keyword("AS");
6971                self.write_space();
6972            }
6973            self.generate_identifier(alias)?;
6974        }
6975
6976        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6977        if !delete.tables_from_using {
6978            for join in &delete.joins {
6979                self.generate_join(join)?;
6980            }
6981        }
6982
6983        // USING clause (PostgreSQL/DuckDB/MySQL)
6984        if !delete.using.is_empty() {
6985            self.write_space();
6986            self.write_keyword("USING");
6987            for (i, table) in delete.using.iter().enumerate() {
6988                if i > 0 {
6989                    self.write(",");
6990                }
6991                self.write_space();
6992                // Check if the table has subquery hints (DuckDB USING with subquery)
6993                if !table.hints.is_empty() && table.name.is_empty() {
6994                    // Subquery in USING: (VALUES ...) AS alias(cols)
6995                    self.generate_expression(&table.hints[0])?;
6996                    if let Some(ref alias) = table.alias {
6997                        self.write_space();
6998                        if table.alias_explicit_as {
6999                            self.write_keyword("AS");
7000                            self.write_space();
7001                        }
7002                        self.generate_identifier(alias)?;
7003                        if !table.column_aliases.is_empty() {
7004                            self.write("(");
7005                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7006                                if j > 0 {
7007                                    self.write(", ");
7008                                }
7009                                self.generate_identifier(col_alias)?;
7010                            }
7011                            self.write(")");
7012                        }
7013                    }
7014                } else {
7015                    self.generate_table(table)?;
7016                }
7017            }
7018        }
7019
7020        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7021        if delete.tables_from_using {
7022            for join in &delete.joins {
7023                self.generate_join(join)?;
7024            }
7025        }
7026
7027        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7028        let output_already_emitted =
7029            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7030        if !output_already_emitted {
7031            if let Some(ref output) = delete.output {
7032                self.generate_output_clause(output)?;
7033            }
7034        }
7035
7036        if let Some(where_clause) = &delete.where_clause {
7037            self.write_space();
7038            self.write_keyword("WHERE");
7039            self.write_space();
7040            self.generate_expression(&where_clause.this)?;
7041        }
7042
7043        // ORDER BY clause (MySQL)
7044        if let Some(ref order_by) = delete.order_by {
7045            self.write_space();
7046            self.generate_order_by(order_by)?;
7047        }
7048
7049        // LIMIT clause (MySQL)
7050        if let Some(ref limit) = delete.limit {
7051            self.write_space();
7052            self.write_keyword("LIMIT");
7053            self.write_space();
7054            self.generate_expression(limit)?;
7055        }
7056
7057        // RETURNING clause (PostgreSQL)
7058        if !delete.returning.is_empty() {
7059            self.write_space();
7060            self.write_keyword("RETURNING");
7061            self.write_space();
7062            for (i, expr) in delete.returning.iter().enumerate() {
7063                if i > 0 {
7064                    self.write(", ");
7065                }
7066                self.generate_expression(expr)?;
7067            }
7068        }
7069
7070        Ok(())
7071    }
7072
7073    // ==================== DDL Generation ====================
7074
7075    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7076        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7077        // CREATE TABLE AS SELECT uses Trino (double quotes)
7078        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7079        let saved_athena_hive_context = self.athena_hive_context;
7080        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7081        if matches!(
7082            self.config.dialect,
7083            Some(crate::dialects::DialectType::Athena)
7084        ) {
7085            // Use Hive context if:
7086            // 1. It's an EXTERNAL table, OR
7087            // 2. There's no AS SELECT clause
7088            let is_external = ct
7089                .table_modifier
7090                .as_ref()
7091                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7092                .unwrap_or(false);
7093            let has_as_select = ct.as_select.is_some();
7094            self.athena_hive_context = is_external || !has_as_select;
7095        }
7096
7097        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7098        if matches!(
7099            self.config.dialect,
7100            Some(crate::dialects::DialectType::TSQL)
7101        ) {
7102            if let Some(ref query) = ct.as_select {
7103                // Output WITH CTE clause if present
7104                if let Some(with_cte) = &ct.with_cte {
7105                    self.generate_with(with_cte)?;
7106                    self.write_space();
7107                }
7108
7109                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7110                self.write_keyword("SELECT");
7111                self.write(" * ");
7112                self.write_keyword("INTO");
7113                self.write_space();
7114
7115                // If temporary, prefix with # for TSQL temp table
7116                if ct.temporary {
7117                    self.write("#");
7118                }
7119                self.generate_table(&ct.name)?;
7120
7121                self.write_space();
7122                self.write_keyword("FROM");
7123                self.write(" (");
7124                // For TSQL, add aliases to select columns to preserve column names
7125                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7126                self.generate_expression(&aliased_query)?;
7127                self.write(") ");
7128                self.write_keyword("AS");
7129                self.write(" temp");
7130                return Ok(());
7131            }
7132        }
7133
7134        // Output WITH CTE clause if present
7135        if let Some(with_cte) = &ct.with_cte {
7136            self.generate_with(with_cte)?;
7137            self.write_space();
7138        }
7139
7140        // Output leading comments before CREATE
7141        for comment in &ct.leading_comments {
7142            self.write_formatted_comment(comment);
7143            self.write(" ");
7144        }
7145        self.write_keyword("CREATE");
7146
7147        if ct.or_replace {
7148            self.write_space();
7149            self.write_keyword("OR REPLACE");
7150        }
7151
7152        if ct.temporary {
7153            self.write_space();
7154            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7155            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7156                self.write_keyword("GLOBAL TEMPORARY");
7157            } else {
7158                self.write_keyword("TEMPORARY");
7159            }
7160        }
7161
7162        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7163        let is_dictionary = ct
7164            .table_modifier
7165            .as_ref()
7166            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7167            .unwrap_or(false);
7168        if let Some(ref modifier) = ct.table_modifier {
7169            // TRANSIENT is Snowflake-specific - skip for other dialects
7170            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7171                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7172            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7173            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7174                || modifier.eq_ignore_ascii_case("SET")
7175                || modifier.eq_ignore_ascii_case("MULTISET")
7176                || modifier.to_uppercase().contains("VOLATILE")
7177                || modifier.to_uppercase().starts_with("SET ")
7178                || modifier.to_uppercase().starts_with("MULTISET ");
7179            let skip_teradata =
7180                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7181            if !skip_transient && !skip_teradata {
7182                self.write_space();
7183                self.write_keyword(modifier);
7184            }
7185        }
7186
7187        if !is_dictionary {
7188            self.write_space();
7189            self.write_keyword("TABLE");
7190        }
7191
7192        if ct.if_not_exists {
7193            self.write_space();
7194            self.write_keyword("IF NOT EXISTS");
7195        }
7196
7197        self.write_space();
7198        self.generate_table(&ct.name)?;
7199
7200        // ClickHouse: ON CLUSTER clause
7201        if let Some(ref on_cluster) = ct.on_cluster {
7202            self.write_space();
7203            self.generate_on_cluster(on_cluster)?;
7204        }
7205
7206        // Teradata: options after table name before column list (comma-separated)
7207        if matches!(
7208            self.config.dialect,
7209            Some(crate::dialects::DialectType::Teradata)
7210        ) && !ct.teradata_post_name_options.is_empty()
7211        {
7212            for opt in &ct.teradata_post_name_options {
7213                self.write(", ");
7214                self.write(opt);
7215            }
7216        }
7217
7218        // Snowflake: COPY GRANTS clause
7219        if ct.copy_grants {
7220            self.write_space();
7221            self.write_keyword("COPY GRANTS");
7222        }
7223
7224        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7225        if let Some(ref using_template) = ct.using_template {
7226            self.write_space();
7227            self.write_keyword("USING TEMPLATE");
7228            self.write_space();
7229            self.generate_expression(using_template)?;
7230            return Ok(());
7231        }
7232
7233        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7234        if let Some(ref clone_source) = ct.clone_source {
7235            self.write_space();
7236            if ct.is_copy && self.config.supports_table_copy {
7237                // BigQuery uses COPY
7238                self.write_keyword("COPY");
7239            } else if ct.shallow_clone {
7240                self.write_keyword("SHALLOW CLONE");
7241            } else {
7242                self.write_keyword("CLONE");
7243            }
7244            self.write_space();
7245            self.generate_table(clone_source)?;
7246            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7247            if let Some(ref at_clause) = ct.clone_at_clause {
7248                self.write_space();
7249                self.generate_expression(at_clause)?;
7250            }
7251            return Ok(());
7252        }
7253
7254        // Handle PARTITION OF property
7255        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7256        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7257        if let Some(ref partition_of) = ct.partition_of {
7258            self.write_space();
7259
7260            // Extract the PartitionedOfProperty parts to generate them separately
7261            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7262                // Output: PARTITION OF <table>
7263                self.write_keyword("PARTITION OF");
7264                self.write_space();
7265                self.generate_expression(&pop.this)?;
7266
7267                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7268                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7269                    self.write(" (");
7270                    let mut first = true;
7271                    for col in &ct.columns {
7272                        if !first {
7273                            self.write(", ");
7274                        }
7275                        first = false;
7276                        self.generate_column_def(col)?;
7277                    }
7278                    for constraint in &ct.constraints {
7279                        if !first {
7280                            self.write(", ");
7281                        }
7282                        first = false;
7283                        self.generate_table_constraint(constraint)?;
7284                    }
7285                    self.write(")");
7286                }
7287
7288                // Output partition bound spec: FOR VALUES ... or DEFAULT
7289                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7290                    self.write_space();
7291                    self.write_keyword("FOR VALUES");
7292                    self.write_space();
7293                    self.generate_expression(&pop.expression)?;
7294                } else {
7295                    self.write_space();
7296                    self.write_keyword("DEFAULT");
7297                }
7298            } else {
7299                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7300                self.generate_expression(partition_of)?;
7301
7302                // Output columns/constraints if present
7303                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7304                    self.write(" (");
7305                    let mut first = true;
7306                    for col in &ct.columns {
7307                        if !first {
7308                            self.write(", ");
7309                        }
7310                        first = false;
7311                        self.generate_column_def(col)?;
7312                    }
7313                    for constraint in &ct.constraints {
7314                        if !first {
7315                            self.write(", ");
7316                        }
7317                        first = false;
7318                        self.generate_table_constraint(constraint)?;
7319                    }
7320                    self.write(")");
7321                }
7322            }
7323
7324            // Output table properties (e.g., PARTITION BY RANGE(population))
7325            for prop in &ct.properties {
7326                self.write_space();
7327                self.generate_expression(prop)?;
7328            }
7329
7330            return Ok(());
7331        }
7332
7333        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7334        // This matches Python sqlglot's behavior for SQLite dialect
7335        self.sqlite_inline_pk_columns.clear();
7336        if matches!(
7337            self.config.dialect,
7338            Some(crate::dialects::DialectType::SQLite)
7339        ) {
7340            for constraint in &ct.constraints {
7341                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7342                    // Only inline if: single column, no constraint name, and column exists in table
7343                    if columns.len() == 1 && name.is_none() {
7344                        let pk_col_name = columns[0].name.to_lowercase();
7345                        // Check if this column exists in the table
7346                        if ct
7347                            .columns
7348                            .iter()
7349                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7350                        {
7351                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7352                        }
7353                    }
7354                }
7355            }
7356        }
7357
7358        // Output columns if present (even for CTAS with columns)
7359        if !ct.columns.is_empty() {
7360            if self.config.pretty {
7361                // Pretty print: each column on new line
7362                self.write(" (");
7363                self.write_newline();
7364                self.indent_level += 1;
7365                for (i, col) in ct.columns.iter().enumerate() {
7366                    if i > 0 {
7367                        self.write(",");
7368                        self.write_newline();
7369                    }
7370                    self.write_indent();
7371                    self.generate_column_def(col)?;
7372                }
7373                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7374                for constraint in &ct.constraints {
7375                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7376                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7377                        if columns.len() == 1
7378                            && name.is_none()
7379                            && self
7380                                .sqlite_inline_pk_columns
7381                                .contains(&columns[0].name.to_lowercase())
7382                        {
7383                            continue;
7384                        }
7385                    }
7386                    self.write(",");
7387                    self.write_newline();
7388                    self.write_indent();
7389                    self.generate_table_constraint(constraint)?;
7390                }
7391                self.indent_level -= 1;
7392                self.write_newline();
7393                self.write(")");
7394            } else {
7395                self.write(" (");
7396                for (i, col) in ct.columns.iter().enumerate() {
7397                    if i > 0 {
7398                        self.write(", ");
7399                    }
7400                    self.generate_column_def(col)?;
7401                }
7402                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7403                let mut first_constraint = true;
7404                for constraint in &ct.constraints {
7405                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7406                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7407                        if columns.len() == 1
7408                            && name.is_none()
7409                            && self
7410                                .sqlite_inline_pk_columns
7411                                .contains(&columns[0].name.to_lowercase())
7412                        {
7413                            continue;
7414                        }
7415                    }
7416                    if first_constraint {
7417                        self.write(", ");
7418                        first_constraint = false;
7419                    } else {
7420                        self.write(", ");
7421                    }
7422                    self.generate_table_constraint(constraint)?;
7423                }
7424                self.write(")");
7425            }
7426        } else if !ct.constraints.is_empty() {
7427            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7428            let has_like_only = ct
7429                .constraints
7430                .iter()
7431                .all(|c| matches!(c, TableConstraint::Like { .. }));
7432            let has_tags_only = ct
7433                .constraints
7434                .iter()
7435                .all(|c| matches!(c, TableConstraint::Tags(_)));
7436            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7437            // Most dialects: CREATE TABLE A LIKE B (no parens)
7438            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7439            let is_pg_like = matches!(
7440                self.config.dialect,
7441                Some(crate::dialects::DialectType::PostgreSQL)
7442                    | Some(crate::dialects::DialectType::CockroachDB)
7443                    | Some(crate::dialects::DialectType::Materialize)
7444                    | Some(crate::dialects::DialectType::RisingWave)
7445                    | Some(crate::dialects::DialectType::Redshift)
7446                    | Some(crate::dialects::DialectType::Presto)
7447                    | Some(crate::dialects::DialectType::Trino)
7448                    | Some(crate::dialects::DialectType::Athena)
7449            );
7450            let use_parens = if has_like_only {
7451                is_pg_like
7452            } else {
7453                !has_tags_only
7454            };
7455            if self.config.pretty && use_parens {
7456                self.write(" (");
7457                self.write_newline();
7458                self.indent_level += 1;
7459                for (i, constraint) in ct.constraints.iter().enumerate() {
7460                    if i > 0 {
7461                        self.write(",");
7462                        self.write_newline();
7463                    }
7464                    self.write_indent();
7465                    self.generate_table_constraint(constraint)?;
7466                }
7467                self.indent_level -= 1;
7468                self.write_newline();
7469                self.write(")");
7470            } else {
7471                if use_parens {
7472                    self.write(" (");
7473                } else {
7474                    self.write_space();
7475                }
7476                for (i, constraint) in ct.constraints.iter().enumerate() {
7477                    if i > 0 {
7478                        self.write(", ");
7479                    }
7480                    self.generate_table_constraint(constraint)?;
7481                }
7482                if use_parens {
7483                    self.write(")");
7484                }
7485            }
7486        }
7487
7488        // TSQL ON filegroup or ON filegroup (partition_column) clause
7489        if let Some(ref on_prop) = ct.on_property {
7490            self.write(" ");
7491            self.write_keyword("ON");
7492            self.write(" ");
7493            self.generate_expression(&on_prop.this)?;
7494        }
7495
7496        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7497        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7498        if !is_clickhouse {
7499            for prop in &ct.properties {
7500                if let Expression::SchemaCommentProperty(_) = prop {
7501                    if self.config.pretty {
7502                        self.write_newline();
7503                    } else {
7504                        self.write_space();
7505                    }
7506                    self.generate_expression(prop)?;
7507                }
7508            }
7509        }
7510
7511        // WITH properties (output after columns if columns exist, otherwise before AS)
7512        if !ct.with_properties.is_empty() {
7513            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7514            let is_snowflake_special_table = matches!(
7515                self.config.dialect,
7516                Some(crate::dialects::DialectType::Snowflake)
7517            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7518                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7519            if is_snowflake_special_table {
7520                for (key, value) in &ct.with_properties {
7521                    self.write_space();
7522                    self.write(key);
7523                    self.write("=");
7524                    self.write(value);
7525                }
7526            } else if self.config.pretty {
7527                self.write_newline();
7528                self.write_keyword("WITH");
7529                self.write(" (");
7530                self.write_newline();
7531                self.indent_level += 1;
7532                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7533                    if i > 0 {
7534                        self.write(",");
7535                        self.write_newline();
7536                    }
7537                    self.write_indent();
7538                    self.write(key);
7539                    self.write("=");
7540                    self.write(value);
7541                }
7542                self.indent_level -= 1;
7543                self.write_newline();
7544                self.write(")");
7545            } else {
7546                self.write_space();
7547                self.write_keyword("WITH");
7548                self.write(" (");
7549                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7550                    if i > 0 {
7551                        self.write(", ");
7552                    }
7553                    self.write(key);
7554                    self.write("=");
7555                    self.write(value);
7556                }
7557                self.write(")");
7558            }
7559        }
7560
7561        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7562            if is_clickhouse && ct.as_select.is_some() {
7563                let mut pre = Vec::new();
7564                let mut post = Vec::new();
7565                for prop in &ct.properties {
7566                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7567                        post.push(prop);
7568                    } else {
7569                        pre.push(prop);
7570                    }
7571                }
7572                (pre, post)
7573            } else {
7574                (ct.properties.iter().collect(), Vec::new())
7575            };
7576
7577        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7578        for prop in pre_as_properties {
7579            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7580            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7581                continue;
7582            }
7583            if self.config.pretty {
7584                self.write_newline();
7585            } else {
7586                self.write_space();
7587            }
7588            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7589            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7590            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7591            if let Expression::Properties(props) = prop {
7592                let is_hive_dialect = matches!(
7593                    self.config.dialect,
7594                    Some(crate::dialects::DialectType::Hive)
7595                        | Some(crate::dialects::DialectType::Spark)
7596                        | Some(crate::dialects::DialectType::Databricks)
7597                        | Some(crate::dialects::DialectType::Athena)
7598                );
7599                let is_doris_starrocks = matches!(
7600                    self.config.dialect,
7601                    Some(crate::dialects::DialectType::Doris)
7602                        | Some(crate::dialects::DialectType::StarRocks)
7603                );
7604                if is_hive_dialect {
7605                    self.generate_tblproperties_clause(&props.expressions)?;
7606                } else if is_doris_starrocks {
7607                    self.generate_properties_clause(&props.expressions)?;
7608                } else {
7609                    self.generate_options_clause(&props.expressions)?;
7610                }
7611            } else {
7612                self.generate_expression(prop)?;
7613            }
7614        }
7615
7616        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7617        for prop in &ct.post_table_properties {
7618            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7619                self.write(" WITH(");
7620                self.generate_system_versioning_content(svp)?;
7621                self.write(")");
7622            } else if let Expression::Properties(props) = prop {
7623                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7624                let is_doris_starrocks = matches!(
7625                    self.config.dialect,
7626                    Some(crate::dialects::DialectType::Doris)
7627                        | Some(crate::dialects::DialectType::StarRocks)
7628                );
7629                self.write_space();
7630                if is_doris_starrocks {
7631                    self.generate_properties_clause(&props.expressions)?;
7632                } else {
7633                    self.generate_options_clause(&props.expressions)?;
7634                }
7635            } else {
7636                self.write_space();
7637                self.generate_expression(prop)?;
7638            }
7639        }
7640
7641        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7642        // Only output for StarRocks target
7643        if let Some(ref rollup) = ct.rollup {
7644            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7645                self.write_space();
7646                self.generate_rollup_property(rollup)?;
7647            }
7648        }
7649
7650        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7651        // Only output for MySQL-compatible dialects; strip for others during transpilation
7652        // COMMENT is also used by Hive/Spark so we selectively preserve it
7653        let is_mysql_compatible = matches!(
7654            self.config.dialect,
7655            Some(DialectType::MySQL)
7656                | Some(DialectType::SingleStore)
7657                | Some(DialectType::Doris)
7658                | Some(DialectType::StarRocks)
7659                | None
7660        );
7661        let is_hive_compatible = matches!(
7662            self.config.dialect,
7663            Some(DialectType::Hive)
7664                | Some(DialectType::Spark)
7665                | Some(DialectType::Databricks)
7666                | Some(DialectType::Athena)
7667        );
7668        let mysql_pretty_options =
7669            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7670        for (key, value) in &ct.mysql_table_options {
7671            // Skip non-MySQL-specific options for non-MySQL targets
7672            let should_output = if is_mysql_compatible {
7673                true
7674            } else if is_hive_compatible && key == "COMMENT" {
7675                true // COMMENT is valid in Hive/Spark table definitions
7676            } else {
7677                false
7678            };
7679            if should_output {
7680                if mysql_pretty_options {
7681                    self.write_newline();
7682                    self.write_indent();
7683                } else {
7684                    self.write_space();
7685                }
7686                self.write_keyword(key);
7687                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7688                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7689                    self.write_space();
7690                } else {
7691                    self.write("=");
7692                }
7693                self.write(value);
7694            }
7695        }
7696
7697        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7698        if ct.temporary
7699            && matches!(
7700                self.config.dialect,
7701                Some(DialectType::Spark) | Some(DialectType::Databricks)
7702            )
7703            && ct.as_select.is_none()
7704        {
7705            self.write_space();
7706            self.write_keyword("USING PARQUET");
7707        }
7708
7709        // PostgreSQL INHERITS clause
7710        if !ct.inherits.is_empty() {
7711            self.write_space();
7712            self.write_keyword("INHERITS");
7713            self.write(" (");
7714            for (i, parent) in ct.inherits.iter().enumerate() {
7715                if i > 0 {
7716                    self.write(", ");
7717                }
7718                self.generate_table(parent)?;
7719            }
7720            self.write(")");
7721        }
7722
7723        // CREATE TABLE AS SELECT
7724        if let Some(ref query) = ct.as_select {
7725            self.write_space();
7726            self.write_keyword("AS");
7727            self.write_space();
7728            if ct.as_select_parenthesized {
7729                self.write("(");
7730            }
7731            self.generate_expression(query)?;
7732            if ct.as_select_parenthesized {
7733                self.write(")");
7734            }
7735
7736            // Teradata: WITH DATA / WITH NO DATA
7737            if let Some(with_data) = ct.with_data {
7738                self.write_space();
7739                self.write_keyword("WITH");
7740                if !with_data {
7741                    self.write_space();
7742                    self.write_keyword("NO");
7743                }
7744                self.write_space();
7745                self.write_keyword("DATA");
7746            }
7747
7748            // Teradata: AND STATISTICS / AND NO STATISTICS
7749            if let Some(with_statistics) = ct.with_statistics {
7750                self.write_space();
7751                self.write_keyword("AND");
7752                if !with_statistics {
7753                    self.write_space();
7754                    self.write_keyword("NO");
7755                }
7756                self.write_space();
7757                self.write_keyword("STATISTICS");
7758            }
7759
7760            // Teradata: Index specifications
7761            for index in &ct.teradata_indexes {
7762                self.write_space();
7763                match index.kind {
7764                    TeradataIndexKind::NoPrimary => {
7765                        self.write_keyword("NO PRIMARY INDEX");
7766                    }
7767                    TeradataIndexKind::Primary => {
7768                        self.write_keyword("PRIMARY INDEX");
7769                    }
7770                    TeradataIndexKind::PrimaryAmp => {
7771                        self.write_keyword("PRIMARY AMP INDEX");
7772                    }
7773                    TeradataIndexKind::Unique => {
7774                        self.write_keyword("UNIQUE INDEX");
7775                    }
7776                    TeradataIndexKind::UniquePrimary => {
7777                        self.write_keyword("UNIQUE PRIMARY INDEX");
7778                    }
7779                    TeradataIndexKind::Secondary => {
7780                        self.write_keyword("INDEX");
7781                    }
7782                }
7783                // Output index name if present
7784                if let Some(ref name) = index.name {
7785                    self.write_space();
7786                    self.write(name);
7787                }
7788                // Output columns if present
7789                if !index.columns.is_empty() {
7790                    self.write(" (");
7791                    for (i, col) in index.columns.iter().enumerate() {
7792                        if i > 0 {
7793                            self.write(", ");
7794                        }
7795                        self.write(col);
7796                    }
7797                    self.write(")");
7798                }
7799            }
7800
7801            // Teradata: ON COMMIT behavior for volatile tables
7802            if let Some(ref on_commit) = ct.on_commit {
7803                self.write_space();
7804                self.write_keyword("ON COMMIT");
7805                self.write_space();
7806                match on_commit {
7807                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7808                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7809                }
7810            }
7811
7812            if !post_as_properties.is_empty() {
7813                for prop in post_as_properties {
7814                    self.write_space();
7815                    self.generate_expression(prop)?;
7816                }
7817            }
7818
7819            // Restore Athena Hive context before early return
7820            self.athena_hive_context = saved_athena_hive_context;
7821            return Ok(());
7822        }
7823
7824        // ON COMMIT behavior (for non-CTAS tables)
7825        if let Some(ref on_commit) = ct.on_commit {
7826            self.write_space();
7827            self.write_keyword("ON COMMIT");
7828            self.write_space();
7829            match on_commit {
7830                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7831                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7832            }
7833        }
7834
7835        // Restore Athena Hive context
7836        self.athena_hive_context = saved_athena_hive_context;
7837
7838        Ok(())
7839    }
7840
7841    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7842    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7843    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7844        // Output column name
7845        self.generate_identifier(&col.name)?;
7846        // Output data type if known
7847        if !matches!(col.data_type, DataType::Unknown) {
7848            self.write_space();
7849            self.generate_data_type(&col.data_type)?;
7850        }
7851        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7852        for constraint in &col.constraints {
7853            if let ColumnConstraint::Path(path_expr) = constraint {
7854                self.write_space();
7855                self.write_keyword("PATH");
7856                self.write_space();
7857                self.generate_expression(path_expr)?;
7858            }
7859        }
7860        Ok(())
7861    }
7862
7863    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7864        // Check if this is a TSQL computed column (no data type)
7865        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7866            && col
7867                .constraints
7868                .iter()
7869                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7870        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7871        let omit_computed_type = !self.config.computed_column_with_type
7872            && col
7873                .constraints
7874                .iter()
7875                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7876
7877        // Check if this is a partition column spec (no data type, type is Unknown)
7878        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7879        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7880
7881        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7882        // Also check the no_type flag for SQLite columns without types
7883        let has_no_type = col.no_type
7884            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7885                && col.constraints.is_empty());
7886
7887        self.generate_identifier(&col.name)?;
7888
7889        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7890        let serial_expansion = if matches!(
7891            self.config.dialect,
7892            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7893        ) {
7894            if let DataType::Custom { ref name } = col.data_type {
7895                match name.to_uppercase().as_str() {
7896                    "SERIAL" => Some("INT"),
7897                    "BIGSERIAL" => Some("BIGINT"),
7898                    "SMALLSERIAL" => Some("SMALLINT"),
7899                    _ => None,
7900                }
7901            } else {
7902                None
7903            }
7904        } else {
7905            None
7906        };
7907
7908        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7909        {
7910            self.write_space();
7911            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7912            // since ClickHouse uses explicit Nullable() in its type system.
7913            let saved_nullable_depth = self.clickhouse_nullable_depth;
7914            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7915                self.clickhouse_nullable_depth = -1;
7916            }
7917            if let Some(int_type) = serial_expansion {
7918                // SERIAL -> INT (+ constraints added below)
7919                self.write_keyword(int_type);
7920            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7921                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7922                let unsigned_type = match &col.data_type {
7923                    DataType::Int { .. } => Some("UINTEGER"),
7924                    DataType::BigInt { .. } => Some("UBIGINT"),
7925                    DataType::SmallInt { .. } => Some("USMALLINT"),
7926                    DataType::TinyInt { .. } => Some("UTINYINT"),
7927                    _ => None,
7928                };
7929                if let Some(utype) = unsigned_type {
7930                    self.write_keyword(utype);
7931                } else {
7932                    self.generate_data_type(&col.data_type)?;
7933                }
7934            } else {
7935                self.generate_data_type(&col.data_type)?;
7936            }
7937            self.clickhouse_nullable_depth = saved_nullable_depth;
7938        }
7939
7940        // MySQL type modifiers (must come right after data type)
7941        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7942        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7943            self.write_space();
7944            self.write_keyword("UNSIGNED");
7945        }
7946        if col.zerofill {
7947            self.write_space();
7948            self.write_keyword("ZEROFILL");
7949        }
7950
7951        // Teradata column attributes (must come right after data type, in specific order)
7952        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7953
7954        if let Some(ref charset) = col.character_set {
7955            self.write_space();
7956            self.write_keyword("CHARACTER SET");
7957            self.write_space();
7958            self.write(charset);
7959        }
7960
7961        if col.uppercase {
7962            self.write_space();
7963            self.write_keyword("UPPERCASE");
7964        }
7965
7966        if let Some(casespecific) = col.casespecific {
7967            self.write_space();
7968            if casespecific {
7969                self.write_keyword("CASESPECIFIC");
7970            } else {
7971                self.write_keyword("NOT CASESPECIFIC");
7972            }
7973        }
7974
7975        if let Some(ref format) = col.format {
7976            self.write_space();
7977            self.write_keyword("FORMAT");
7978            self.write(" '");
7979            self.write(format);
7980            self.write("'");
7981        }
7982
7983        if let Some(ref title) = col.title {
7984            self.write_space();
7985            self.write_keyword("TITLE");
7986            self.write(" '");
7987            self.write(title);
7988            self.write("'");
7989        }
7990
7991        if let Some(length) = col.inline_length {
7992            self.write_space();
7993            self.write_keyword("INLINE LENGTH");
7994            self.write(" ");
7995            self.write(&length.to_string());
7996        }
7997
7998        if let Some(ref compress) = col.compress {
7999            self.write_space();
8000            self.write_keyword("COMPRESS");
8001            if !compress.is_empty() {
8002                // Single string literal: output without parentheses (Teradata syntax)
8003                if compress.len() == 1 {
8004                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
8005                        self.write_space();
8006                        self.generate_expression(&compress[0])?;
8007                    } else {
8008                        self.write(" (");
8009                        self.generate_expression(&compress[0])?;
8010                        self.write(")");
8011                    }
8012                } else {
8013                    self.write(" (");
8014                    for (i, val) in compress.iter().enumerate() {
8015                        if i > 0 {
8016                            self.write(", ");
8017                        }
8018                        self.generate_expression(val)?;
8019                    }
8020                    self.write(")");
8021                }
8022            }
8023        }
8024
8025        // Column constraints - output in original order if constraint_order is populated
8026        // Otherwise fall back to legacy fixed order for backward compatibility
8027        if !col.constraint_order.is_empty() {
8028            // Use constraint_order for original ordering
8029            // Track indices for constraints stored in the constraints Vec
8030            let mut references_idx = 0;
8031            let mut check_idx = 0;
8032            let mut generated_idx = 0;
8033            let mut collate_idx = 0;
8034            let mut comment_idx = 0;
8035            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8036            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8037            let defer_not_null_after_identity = false;
8038            let mut pending_not_null_after_identity = false;
8039
8040            for constraint_type in &col.constraint_order {
8041                match constraint_type {
8042                    ConstraintType::PrimaryKey => {
8043                        // Materialize doesn't support PRIMARY KEY column constraints
8044                        if col.primary_key
8045                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8046                        {
8047                            if let Some(ref cname) = col.primary_key_constraint_name {
8048                                self.write_space();
8049                                self.write_keyword("CONSTRAINT");
8050                                self.write_space();
8051                                self.write(cname);
8052                            }
8053                            self.write_space();
8054                            self.write_keyword("PRIMARY KEY");
8055                            if let Some(ref order) = col.primary_key_order {
8056                                self.write_space();
8057                                match order {
8058                                    SortOrder::Asc => self.write_keyword("ASC"),
8059                                    SortOrder::Desc => self.write_keyword("DESC"),
8060                                }
8061                            }
8062                        }
8063                    }
8064                    ConstraintType::Unique => {
8065                        if col.unique {
8066                            if let Some(ref cname) = col.unique_constraint_name {
8067                                self.write_space();
8068                                self.write_keyword("CONSTRAINT");
8069                                self.write_space();
8070                                self.write(cname);
8071                            }
8072                            self.write_space();
8073                            self.write_keyword("UNIQUE");
8074                            // PostgreSQL 15+: NULLS NOT DISTINCT
8075                            if col.unique_nulls_not_distinct {
8076                                self.write(" NULLS NOT DISTINCT");
8077                            }
8078                        }
8079                    }
8080                    ConstraintType::NotNull => {
8081                        if col.nullable == Some(false) {
8082                            if defer_not_null_after_identity {
8083                                pending_not_null_after_identity = true;
8084                                continue;
8085                            }
8086                            if let Some(ref cname) = col.not_null_constraint_name {
8087                                self.write_space();
8088                                self.write_keyword("CONSTRAINT");
8089                                self.write_space();
8090                                self.write(cname);
8091                            }
8092                            self.write_space();
8093                            self.write_keyword("NOT NULL");
8094                        }
8095                    }
8096                    ConstraintType::Null => {
8097                        if col.nullable == Some(true) {
8098                            self.write_space();
8099                            self.write_keyword("NULL");
8100                        }
8101                    }
8102                    ConstraintType::Default => {
8103                        if let Some(ref default) = col.default {
8104                            self.write_space();
8105                            self.write_keyword("DEFAULT");
8106                            self.write_space();
8107                            self.generate_expression(default)?;
8108                        }
8109                    }
8110                    ConstraintType::AutoIncrement => {
8111                        if col.auto_increment {
8112                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8113                            if matches!(
8114                                self.config.dialect,
8115                                Some(crate::dialects::DialectType::DuckDB)
8116                            ) {
8117                                // Skip - DuckDB uses sequences or rowid instead
8118                            } else if matches!(
8119                                self.config.dialect,
8120                                Some(crate::dialects::DialectType::Materialize)
8121                            ) {
8122                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8123                                if !matches!(col.nullable, Some(false)) {
8124                                    self.write_space();
8125                                    self.write_keyword("NOT NULL");
8126                                }
8127                            } else if matches!(
8128                                self.config.dialect,
8129                                Some(crate::dialects::DialectType::PostgreSQL)
8130                            ) {
8131                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8132                                self.write_space();
8133                                self.generate_auto_increment_keyword(col)?;
8134                            } else {
8135                                self.write_space();
8136                                self.generate_auto_increment_keyword(col)?;
8137                                if pending_not_null_after_identity {
8138                                    self.write_space();
8139                                    self.write_keyword("NOT NULL");
8140                                    pending_not_null_after_identity = false;
8141                                }
8142                            }
8143                        } // close else for DuckDB skip
8144                    }
8145                    ConstraintType::References => {
8146                        // Find next References constraint
8147                        while references_idx < col.constraints.len() {
8148                            if let ColumnConstraint::References(fk_ref) =
8149                                &col.constraints[references_idx]
8150                            {
8151                                // CONSTRAINT name if present
8152                                if let Some(ref name) = fk_ref.constraint_name {
8153                                    self.write_space();
8154                                    self.write_keyword("CONSTRAINT");
8155                                    self.write_space();
8156                                    self.write(name);
8157                                }
8158                                self.write_space();
8159                                if fk_ref.has_foreign_key_keywords {
8160                                    self.write_keyword("FOREIGN KEY");
8161                                    self.write_space();
8162                                }
8163                                self.write_keyword("REFERENCES");
8164                                self.write_space();
8165                                self.generate_table(&fk_ref.table)?;
8166                                if !fk_ref.columns.is_empty() {
8167                                    self.write(" (");
8168                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8169                                        if i > 0 {
8170                                            self.write(", ");
8171                                        }
8172                                        self.generate_identifier(c)?;
8173                                    }
8174                                    self.write(")");
8175                                }
8176                                self.generate_referential_actions(fk_ref)?;
8177                                references_idx += 1;
8178                                break;
8179                            }
8180                            references_idx += 1;
8181                        }
8182                    }
8183                    ConstraintType::Check => {
8184                        // Find next Check constraint
8185                        while check_idx < col.constraints.len() {
8186                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8187                                // Output CONSTRAINT name if present (only for first CHECK)
8188                                if check_idx == 0 {
8189                                    if let Some(ref cname) = col.check_constraint_name {
8190                                        self.write_space();
8191                                        self.write_keyword("CONSTRAINT");
8192                                        self.write_space();
8193                                        self.write(cname);
8194                                    }
8195                                }
8196                                self.write_space();
8197                                self.write_keyword("CHECK");
8198                                self.write(" (");
8199                                self.generate_expression(expr)?;
8200                                self.write(")");
8201                                check_idx += 1;
8202                                break;
8203                            }
8204                            check_idx += 1;
8205                        }
8206                    }
8207                    ConstraintType::GeneratedAsIdentity => {
8208                        // Find next GeneratedAsIdentity constraint
8209                        while generated_idx < col.constraints.len() {
8210                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8211                                &col.constraints[generated_idx]
8212                            {
8213                                self.write_space();
8214                                // Redshift uses IDENTITY(start, increment) syntax
8215                                if matches!(
8216                                    self.config.dialect,
8217                                    Some(crate::dialects::DialectType::Redshift)
8218                                ) {
8219                                    self.write_keyword("IDENTITY");
8220                                    self.write("(");
8221                                    if let Some(ref start) = gen.start {
8222                                        self.generate_expression(start)?;
8223                                    } else {
8224                                        self.write("0");
8225                                    }
8226                                    self.write(", ");
8227                                    if let Some(ref incr) = gen.increment {
8228                                        self.generate_expression(incr)?;
8229                                    } else {
8230                                        self.write("1");
8231                                    }
8232                                    self.write(")");
8233                                } else {
8234                                    self.write_keyword("GENERATED");
8235                                    if gen.always {
8236                                        self.write_space();
8237                                        self.write_keyword("ALWAYS");
8238                                    } else {
8239                                        self.write_space();
8240                                        self.write_keyword("BY DEFAULT");
8241                                        if gen.on_null {
8242                                            self.write_space();
8243                                            self.write_keyword("ON NULL");
8244                                        }
8245                                    }
8246                                    self.write_space();
8247                                    self.write_keyword("AS IDENTITY");
8248
8249                                    let has_options = gen.start.is_some()
8250                                        || gen.increment.is_some()
8251                                        || gen.minvalue.is_some()
8252                                        || gen.maxvalue.is_some()
8253                                        || gen.cycle.is_some();
8254                                    if has_options {
8255                                        self.write(" (");
8256                                        let mut first = true;
8257                                        if let Some(ref start) = gen.start {
8258                                            if !first {
8259                                                self.write(" ");
8260                                            }
8261                                            first = false;
8262                                            self.write_keyword("START WITH");
8263                                            self.write_space();
8264                                            self.generate_expression(start)?;
8265                                        }
8266                                        if let Some(ref incr) = gen.increment {
8267                                            if !first {
8268                                                self.write(" ");
8269                                            }
8270                                            first = false;
8271                                            self.write_keyword("INCREMENT BY");
8272                                            self.write_space();
8273                                            self.generate_expression(incr)?;
8274                                        }
8275                                        if let Some(ref minv) = gen.minvalue {
8276                                            if !first {
8277                                                self.write(" ");
8278                                            }
8279                                            first = false;
8280                                            self.write_keyword("MINVALUE");
8281                                            self.write_space();
8282                                            self.generate_expression(minv)?;
8283                                        }
8284                                        if let Some(ref maxv) = gen.maxvalue {
8285                                            if !first {
8286                                                self.write(" ");
8287                                            }
8288                                            first = false;
8289                                            self.write_keyword("MAXVALUE");
8290                                            self.write_space();
8291                                            self.generate_expression(maxv)?;
8292                                        }
8293                                        if let Some(cycle) = gen.cycle {
8294                                            if !first {
8295                                                self.write(" ");
8296                                            }
8297                                            if cycle {
8298                                                self.write_keyword("CYCLE");
8299                                            } else {
8300                                                self.write_keyword("NO CYCLE");
8301                                            }
8302                                        }
8303                                        self.write(")");
8304                                    }
8305                                }
8306                                generated_idx += 1;
8307                                break;
8308                            }
8309                            generated_idx += 1;
8310                        }
8311                    }
8312                    ConstraintType::Collate => {
8313                        // Find next Collate constraint
8314                        while collate_idx < col.constraints.len() {
8315                            if let ColumnConstraint::Collate(collation) =
8316                                &col.constraints[collate_idx]
8317                            {
8318                                self.write_space();
8319                                self.write_keyword("COLLATE");
8320                                self.write_space();
8321                                self.generate_identifier(collation)?;
8322                                collate_idx += 1;
8323                                break;
8324                            }
8325                            collate_idx += 1;
8326                        }
8327                    }
8328                    ConstraintType::Comment => {
8329                        // Find next Comment constraint
8330                        while comment_idx < col.constraints.len() {
8331                            if let ColumnConstraint::Comment(comment) =
8332                                &col.constraints[comment_idx]
8333                            {
8334                                self.write_space();
8335                                self.write_keyword("COMMENT");
8336                                self.write_space();
8337                                self.generate_string_literal(comment)?;
8338                                comment_idx += 1;
8339                                break;
8340                            }
8341                            comment_idx += 1;
8342                        }
8343                    }
8344                    ConstraintType::Tags => {
8345                        // Find next Tags constraint (Snowflake)
8346                        for constraint in &col.constraints {
8347                            if let ColumnConstraint::Tags(tags) = constraint {
8348                                self.write_space();
8349                                self.write_keyword("TAG");
8350                                self.write(" (");
8351                                for (i, expr) in tags.expressions.iter().enumerate() {
8352                                    if i > 0 {
8353                                        self.write(", ");
8354                                    }
8355                                    self.generate_expression(expr)?;
8356                                }
8357                                self.write(")");
8358                                break;
8359                            }
8360                        }
8361                    }
8362                    ConstraintType::ComputedColumn => {
8363                        // Find next ComputedColumn constraint
8364                        for constraint in &col.constraints {
8365                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8366                                self.write_space();
8367                                self.generate_computed_column_inline(cc)?;
8368                                break;
8369                            }
8370                        }
8371                    }
8372                    ConstraintType::GeneratedAsRow => {
8373                        // Find next GeneratedAsRow constraint
8374                        for constraint in &col.constraints {
8375                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8376                                self.write_space();
8377                                self.generate_generated_as_row_inline(gar)?;
8378                                break;
8379                            }
8380                        }
8381                    }
8382                    ConstraintType::OnUpdate => {
8383                        if let Some(ref expr) = col.on_update {
8384                            self.write_space();
8385                            self.write_keyword("ON UPDATE");
8386                            self.write_space();
8387                            self.generate_expression(expr)?;
8388                        }
8389                    }
8390                    ConstraintType::Encode => {
8391                        if let Some(ref encoding) = col.encoding {
8392                            self.write_space();
8393                            self.write_keyword("ENCODE");
8394                            self.write_space();
8395                            self.write(encoding);
8396                        }
8397                    }
8398                    ConstraintType::Path => {
8399                        // Find next Path constraint
8400                        for constraint in &col.constraints {
8401                            if let ColumnConstraint::Path(path_expr) = constraint {
8402                                self.write_space();
8403                                self.write_keyword("PATH");
8404                                self.write_space();
8405                                self.generate_expression(path_expr)?;
8406                                break;
8407                            }
8408                        }
8409                    }
8410                }
8411            }
8412            if pending_not_null_after_identity {
8413                self.write_space();
8414                self.write_keyword("NOT NULL");
8415            }
8416        } else {
8417            // Legacy fixed order for backward compatibility
8418            if col.primary_key {
8419                self.write_space();
8420                self.write_keyword("PRIMARY KEY");
8421                if let Some(ref order) = col.primary_key_order {
8422                    self.write_space();
8423                    match order {
8424                        SortOrder::Asc => self.write_keyword("ASC"),
8425                        SortOrder::Desc => self.write_keyword("DESC"),
8426                    }
8427                }
8428            }
8429
8430            if col.unique {
8431                self.write_space();
8432                self.write_keyword("UNIQUE");
8433                // PostgreSQL 15+: NULLS NOT DISTINCT
8434                if col.unique_nulls_not_distinct {
8435                    self.write(" NULLS NOT DISTINCT");
8436                }
8437            }
8438
8439            match col.nullable {
8440                Some(false) => {
8441                    self.write_space();
8442                    self.write_keyword("NOT NULL");
8443                }
8444                Some(true) => {
8445                    self.write_space();
8446                    self.write_keyword("NULL");
8447                }
8448                None => {}
8449            }
8450
8451            if let Some(ref default) = col.default {
8452                self.write_space();
8453                self.write_keyword("DEFAULT");
8454                self.write_space();
8455                self.generate_expression(default)?;
8456            }
8457
8458            if col.auto_increment {
8459                self.write_space();
8460                self.generate_auto_increment_keyword(col)?;
8461            }
8462
8463            // Column-level constraints from Vec
8464            for constraint in &col.constraints {
8465                match constraint {
8466                    ColumnConstraint::References(fk_ref) => {
8467                        self.write_space();
8468                        if fk_ref.has_foreign_key_keywords {
8469                            self.write_keyword("FOREIGN KEY");
8470                            self.write_space();
8471                        }
8472                        self.write_keyword("REFERENCES");
8473                        self.write_space();
8474                        self.generate_table(&fk_ref.table)?;
8475                        if !fk_ref.columns.is_empty() {
8476                            self.write(" (");
8477                            for (i, c) in fk_ref.columns.iter().enumerate() {
8478                                if i > 0 {
8479                                    self.write(", ");
8480                                }
8481                                self.generate_identifier(c)?;
8482                            }
8483                            self.write(")");
8484                        }
8485                        self.generate_referential_actions(fk_ref)?;
8486                    }
8487                    ColumnConstraint::Check(expr) => {
8488                        self.write_space();
8489                        self.write_keyword("CHECK");
8490                        self.write(" (");
8491                        self.generate_expression(expr)?;
8492                        self.write(")");
8493                    }
8494                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8495                        self.write_space();
8496                        // Redshift uses IDENTITY(start, increment) syntax
8497                        if matches!(
8498                            self.config.dialect,
8499                            Some(crate::dialects::DialectType::Redshift)
8500                        ) {
8501                            self.write_keyword("IDENTITY");
8502                            self.write("(");
8503                            if let Some(ref start) = gen.start {
8504                                self.generate_expression(start)?;
8505                            } else {
8506                                self.write("0");
8507                            }
8508                            self.write(", ");
8509                            if let Some(ref incr) = gen.increment {
8510                                self.generate_expression(incr)?;
8511                            } else {
8512                                self.write("1");
8513                            }
8514                            self.write(")");
8515                        } else {
8516                            self.write_keyword("GENERATED");
8517                            if gen.always {
8518                                self.write_space();
8519                                self.write_keyword("ALWAYS");
8520                            } else {
8521                                self.write_space();
8522                                self.write_keyword("BY DEFAULT");
8523                                if gen.on_null {
8524                                    self.write_space();
8525                                    self.write_keyword("ON NULL");
8526                                }
8527                            }
8528                            self.write_space();
8529                            self.write_keyword("AS IDENTITY");
8530
8531                            let has_options = gen.start.is_some()
8532                                || gen.increment.is_some()
8533                                || gen.minvalue.is_some()
8534                                || gen.maxvalue.is_some()
8535                                || gen.cycle.is_some();
8536                            if has_options {
8537                                self.write(" (");
8538                                let mut first = true;
8539                                if let Some(ref start) = gen.start {
8540                                    if !first {
8541                                        self.write(" ");
8542                                    }
8543                                    first = false;
8544                                    self.write_keyword("START WITH");
8545                                    self.write_space();
8546                                    self.generate_expression(start)?;
8547                                }
8548                                if let Some(ref incr) = gen.increment {
8549                                    if !first {
8550                                        self.write(" ");
8551                                    }
8552                                    first = false;
8553                                    self.write_keyword("INCREMENT BY");
8554                                    self.write_space();
8555                                    self.generate_expression(incr)?;
8556                                }
8557                                if let Some(ref minv) = gen.minvalue {
8558                                    if !first {
8559                                        self.write(" ");
8560                                    }
8561                                    first = false;
8562                                    self.write_keyword("MINVALUE");
8563                                    self.write_space();
8564                                    self.generate_expression(minv)?;
8565                                }
8566                                if let Some(ref maxv) = gen.maxvalue {
8567                                    if !first {
8568                                        self.write(" ");
8569                                    }
8570                                    first = false;
8571                                    self.write_keyword("MAXVALUE");
8572                                    self.write_space();
8573                                    self.generate_expression(maxv)?;
8574                                }
8575                                if let Some(cycle) = gen.cycle {
8576                                    if !first {
8577                                        self.write(" ");
8578                                    }
8579                                    if cycle {
8580                                        self.write_keyword("CYCLE");
8581                                    } else {
8582                                        self.write_keyword("NO CYCLE");
8583                                    }
8584                                }
8585                                self.write(")");
8586                            }
8587                        }
8588                    }
8589                    ColumnConstraint::Collate(collation) => {
8590                        self.write_space();
8591                        self.write_keyword("COLLATE");
8592                        self.write_space();
8593                        self.generate_identifier(collation)?;
8594                    }
8595                    ColumnConstraint::Comment(comment) => {
8596                        self.write_space();
8597                        self.write_keyword("COMMENT");
8598                        self.write_space();
8599                        self.generate_string_literal(comment)?;
8600                    }
8601                    ColumnConstraint::Path(path_expr) => {
8602                        self.write_space();
8603                        self.write_keyword("PATH");
8604                        self.write_space();
8605                        self.generate_expression(path_expr)?;
8606                    }
8607                    _ => {} // Other constraints handled above
8608                }
8609            }
8610
8611            // Redshift: ENCODE encoding_type (legacy path)
8612            if let Some(ref encoding) = col.encoding {
8613                self.write_space();
8614                self.write_keyword("ENCODE");
8615                self.write_space();
8616                self.write(encoding);
8617            }
8618        }
8619
8620        // ClickHouse: CODEC(...)
8621        if let Some(ref codec) = col.codec {
8622            self.write_space();
8623            self.write_keyword("CODEC");
8624            self.write("(");
8625            self.write(codec);
8626            self.write(")");
8627        }
8628
8629        // ClickHouse: EPHEMERAL [expr]
8630        if let Some(ref ephemeral) = col.ephemeral {
8631            self.write_space();
8632            self.write_keyword("EPHEMERAL");
8633            if let Some(ref expr) = ephemeral {
8634                self.write_space();
8635                self.generate_expression(expr)?;
8636            }
8637        }
8638
8639        // ClickHouse: MATERIALIZED expr
8640        if let Some(ref mat_expr) = col.materialized_expr {
8641            self.write_space();
8642            self.write_keyword("MATERIALIZED");
8643            self.write_space();
8644            self.generate_expression(mat_expr)?;
8645        }
8646
8647        // ClickHouse: ALIAS expr
8648        if let Some(ref alias_expr) = col.alias_expr {
8649            self.write_space();
8650            self.write_keyword("ALIAS");
8651            self.write_space();
8652            self.generate_expression(alias_expr)?;
8653        }
8654
8655        // ClickHouse: TTL expr
8656        if let Some(ref ttl_expr) = col.ttl_expr {
8657            self.write_space();
8658            self.write_keyword("TTL");
8659            self.write_space();
8660            self.generate_expression(ttl_expr)?;
8661        }
8662
8663        // TSQL: NOT FOR REPLICATION
8664        if col.not_for_replication
8665            && matches!(
8666                self.config.dialect,
8667                Some(crate::dialects::DialectType::TSQL)
8668                    | Some(crate::dialects::DialectType::Fabric)
8669            )
8670        {
8671            self.write_space();
8672            self.write_keyword("NOT FOR REPLICATION");
8673        }
8674
8675        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8676        if !col.options.is_empty() {
8677            self.write_space();
8678            self.generate_options_clause(&col.options)?;
8679        }
8680
8681        // SQLite: Inline PRIMARY KEY from table constraint
8682        // This comes at the end, after all existing column constraints
8683        if !col.primary_key
8684            && self
8685                .sqlite_inline_pk_columns
8686                .contains(&col.name.name.to_lowercase())
8687        {
8688            self.write_space();
8689            self.write_keyword("PRIMARY KEY");
8690        }
8691
8692        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8693        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8694        if serial_expansion.is_some() {
8695            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8696                self.write_space();
8697                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8698            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8699                self.write_space();
8700                self.write_keyword("NOT NULL");
8701            }
8702        }
8703
8704        Ok(())
8705    }
8706
8707    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8708        match constraint {
8709            TableConstraint::PrimaryKey {
8710                name,
8711                columns,
8712                include_columns,
8713                modifiers,
8714                has_constraint_keyword,
8715            } => {
8716                if let Some(ref n) = name {
8717                    if *has_constraint_keyword {
8718                        self.write_keyword("CONSTRAINT");
8719                        self.write_space();
8720                        self.generate_identifier(n)?;
8721                        self.write_space();
8722                    }
8723                }
8724                self.write_keyword("PRIMARY KEY");
8725                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8726                if let Some(ref clustered) = modifiers.clustered {
8727                    self.write_space();
8728                    self.write_keyword(clustered);
8729                }
8730                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8731                if let Some(ref n) = name {
8732                    if !*has_constraint_keyword {
8733                        self.write_space();
8734                        self.generate_identifier(n)?;
8735                    }
8736                }
8737                self.write(" (");
8738                for (i, col) in columns.iter().enumerate() {
8739                    if i > 0 {
8740                        self.write(", ");
8741                    }
8742                    self.generate_identifier(col)?;
8743                }
8744                self.write(")");
8745                if !include_columns.is_empty() {
8746                    self.write_space();
8747                    self.write_keyword("INCLUDE");
8748                    self.write(" (");
8749                    for (i, col) in include_columns.iter().enumerate() {
8750                        if i > 0 {
8751                            self.write(", ");
8752                        }
8753                        self.generate_identifier(col)?;
8754                    }
8755                    self.write(")");
8756                }
8757                self.generate_constraint_modifiers(modifiers);
8758            }
8759            TableConstraint::Unique {
8760                name,
8761                columns,
8762                columns_parenthesized,
8763                modifiers,
8764                has_constraint_keyword,
8765                nulls_not_distinct,
8766            } => {
8767                if let Some(ref n) = name {
8768                    if *has_constraint_keyword {
8769                        self.write_keyword("CONSTRAINT");
8770                        self.write_space();
8771                        self.generate_identifier(n)?;
8772                        self.write_space();
8773                    }
8774                }
8775                self.write_keyword("UNIQUE");
8776                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8777                if let Some(ref clustered) = modifiers.clustered {
8778                    self.write_space();
8779                    self.write_keyword(clustered);
8780                }
8781                // PostgreSQL 15+: NULLS NOT DISTINCT
8782                if *nulls_not_distinct {
8783                    self.write(" NULLS NOT DISTINCT");
8784                }
8785                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8786                if let Some(ref n) = name {
8787                    if !*has_constraint_keyword {
8788                        self.write_space();
8789                        self.generate_identifier(n)?;
8790                    }
8791                }
8792                if *columns_parenthesized {
8793                    self.write(" (");
8794                    for (i, col) in columns.iter().enumerate() {
8795                        if i > 0 {
8796                            self.write(", ");
8797                        }
8798                        self.generate_identifier(col)?;
8799                    }
8800                    self.write(")");
8801                } else {
8802                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8803                    for col in columns.iter() {
8804                        self.write_space();
8805                        self.generate_identifier(col)?;
8806                    }
8807                }
8808                self.generate_constraint_modifiers(modifiers);
8809            }
8810            TableConstraint::ForeignKey {
8811                name,
8812                columns,
8813                references,
8814                on_delete,
8815                on_update,
8816                modifiers,
8817            } => {
8818                if let Some(ref n) = name {
8819                    self.write_keyword("CONSTRAINT");
8820                    self.write_space();
8821                    self.generate_identifier(n)?;
8822                    self.write_space();
8823                }
8824                self.write_keyword("FOREIGN KEY");
8825                self.write(" (");
8826                for (i, col) in columns.iter().enumerate() {
8827                    if i > 0 {
8828                        self.write(", ");
8829                    }
8830                    self.generate_identifier(col)?;
8831                }
8832                self.write(")");
8833                if let Some(ref refs) = references {
8834                    self.write(" ");
8835                    self.write_keyword("REFERENCES");
8836                    self.write_space();
8837                    self.generate_table(&refs.table)?;
8838                    if !refs.columns.is_empty() {
8839                        if self.config.pretty {
8840                            self.write(" (");
8841                            self.write_newline();
8842                            self.indent_level += 1;
8843                            for (i, col) in refs.columns.iter().enumerate() {
8844                                if i > 0 {
8845                                    self.write(",");
8846                                    self.write_newline();
8847                                }
8848                                self.write_indent();
8849                                self.generate_identifier(col)?;
8850                            }
8851                            self.indent_level -= 1;
8852                            self.write_newline();
8853                            self.write_indent();
8854                            self.write(")");
8855                        } else {
8856                            self.write(" (");
8857                            for (i, col) in refs.columns.iter().enumerate() {
8858                                if i > 0 {
8859                                    self.write(", ");
8860                                }
8861                                self.generate_identifier(col)?;
8862                            }
8863                            self.write(")");
8864                        }
8865                    }
8866                    self.generate_referential_actions(refs)?;
8867                } else {
8868                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8869                    if let Some(ref action) = on_delete {
8870                        self.write_space();
8871                        self.write_keyword("ON DELETE");
8872                        self.write_space();
8873                        self.generate_referential_action(action);
8874                    }
8875                    if let Some(ref action) = on_update {
8876                        self.write_space();
8877                        self.write_keyword("ON UPDATE");
8878                        self.write_space();
8879                        self.generate_referential_action(action);
8880                    }
8881                }
8882                self.generate_constraint_modifiers(modifiers);
8883            }
8884            TableConstraint::Check {
8885                name,
8886                expression,
8887                modifiers,
8888            } => {
8889                if let Some(ref n) = name {
8890                    self.write_keyword("CONSTRAINT");
8891                    self.write_space();
8892                    self.generate_identifier(n)?;
8893                    self.write_space();
8894                }
8895                self.write_keyword("CHECK");
8896                self.write(" (");
8897                self.generate_expression(expression)?;
8898                self.write(")");
8899                self.generate_constraint_modifiers(modifiers);
8900            }
8901            TableConstraint::Index {
8902                name,
8903                columns,
8904                kind,
8905                modifiers,
8906                use_key_keyword,
8907                expression,
8908                index_type,
8909                granularity,
8910            } => {
8911                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8912                if expression.is_some() {
8913                    self.write_keyword("INDEX");
8914                    if let Some(ref n) = name {
8915                        self.write_space();
8916                        self.generate_identifier(n)?;
8917                    }
8918                    if let Some(ref expr) = expression {
8919                        self.write_space();
8920                        self.generate_expression(expr)?;
8921                    }
8922                    if let Some(ref idx_type) = index_type {
8923                        self.write_space();
8924                        self.write_keyword("TYPE");
8925                        self.write_space();
8926                        self.generate_expression(idx_type)?;
8927                    }
8928                    if let Some(ref gran) = granularity {
8929                        self.write_space();
8930                        self.write_keyword("GRANULARITY");
8931                        self.write_space();
8932                        self.generate_expression(gran)?;
8933                    }
8934                } else {
8935                    // Standard INDEX syntax
8936                    // Determine the index keyword to use
8937                    // MySQL normalizes KEY to INDEX
8938                    use crate::dialects::DialectType;
8939                    let index_keyword = if *use_key_keyword
8940                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8941                    {
8942                        "KEY"
8943                    } else {
8944                        "INDEX"
8945                    };
8946
8947                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8948                    if let Some(ref k) = kind {
8949                        self.write_keyword(k);
8950                        // For UNIQUE, don't add INDEX/KEY keyword
8951                        if k != "UNIQUE" {
8952                            self.write_space();
8953                            self.write_keyword(index_keyword);
8954                        }
8955                    } else {
8956                        self.write_keyword(index_keyword);
8957                    }
8958
8959                    // Output USING before name if using_before_columns is true and there's no name
8960                    if modifiers.using_before_columns && name.is_none() {
8961                        if let Some(ref using) = modifiers.using {
8962                            self.write_space();
8963                            self.write_keyword("USING");
8964                            self.write_space();
8965                            self.write_keyword(using);
8966                        }
8967                    }
8968
8969                    // Output index name if present
8970                    if let Some(ref n) = name {
8971                        self.write_space();
8972                        self.generate_identifier(n)?;
8973                    }
8974
8975                    // Output USING after name but before columns if using_before_columns and there's a name
8976                    if modifiers.using_before_columns && name.is_some() {
8977                        if let Some(ref using) = modifiers.using {
8978                            self.write_space();
8979                            self.write_keyword("USING");
8980                            self.write_space();
8981                            self.write_keyword(using);
8982                        }
8983                    }
8984
8985                    // Output columns
8986                    self.write(" (");
8987                    for (i, col) in columns.iter().enumerate() {
8988                        if i > 0 {
8989                            self.write(", ");
8990                        }
8991                        self.generate_identifier(col)?;
8992                    }
8993                    self.write(")");
8994
8995                    // Output USING after columns if not using_before_columns
8996                    if !modifiers.using_before_columns {
8997                        if let Some(ref using) = modifiers.using {
8998                            self.write_space();
8999                            self.write_keyword("USING");
9000                            self.write_space();
9001                            self.write_keyword(using);
9002                        }
9003                    }
9004
9005                    // Output other constraint modifiers (but skip USING since we already handled it)
9006                    self.generate_constraint_modifiers_without_using(modifiers);
9007                }
9008            }
9009            TableConstraint::Projection { name, expression } => {
9010                // ClickHouse: PROJECTION name (SELECT ...)
9011                self.write_keyword("PROJECTION");
9012                self.write_space();
9013                self.generate_identifier(name)?;
9014                self.write(" (");
9015                self.generate_expression(expression)?;
9016                self.write(")");
9017            }
9018            TableConstraint::Like { source, options } => {
9019                self.write_keyword("LIKE");
9020                self.write_space();
9021                self.generate_table(source)?;
9022                for (action, prop) in options {
9023                    self.write_space();
9024                    match action {
9025                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9026                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9027                    }
9028                    self.write_space();
9029                    self.write_keyword(prop);
9030                }
9031            }
9032            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9033                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9034                self.write(" (");
9035                self.generate_identifier(start_col)?;
9036                self.write(", ");
9037                self.generate_identifier(end_col)?;
9038                self.write(")");
9039            }
9040            TableConstraint::Exclude {
9041                name,
9042                using,
9043                elements,
9044                include_columns,
9045                where_clause,
9046                with_params,
9047                using_index_tablespace,
9048                modifiers: _,
9049            } => {
9050                if let Some(ref n) = name {
9051                    self.write_keyword("CONSTRAINT");
9052                    self.write_space();
9053                    self.generate_identifier(n)?;
9054                    self.write_space();
9055                }
9056                self.write_keyword("EXCLUDE");
9057                if let Some(ref method) = using {
9058                    self.write_space();
9059                    self.write_keyword("USING");
9060                    self.write_space();
9061                    self.write(method);
9062                    self.write("(");
9063                } else {
9064                    self.write(" (");
9065                }
9066                for (i, elem) in elements.iter().enumerate() {
9067                    if i > 0 {
9068                        self.write(", ");
9069                    }
9070                    self.write(&elem.expression);
9071                    self.write_space();
9072                    self.write_keyword("WITH");
9073                    self.write_space();
9074                    self.write(&elem.operator);
9075                }
9076                self.write(")");
9077                if !include_columns.is_empty() {
9078                    self.write_space();
9079                    self.write_keyword("INCLUDE");
9080                    self.write(" (");
9081                    for (i, col) in include_columns.iter().enumerate() {
9082                        if i > 0 {
9083                            self.write(", ");
9084                        }
9085                        self.generate_identifier(col)?;
9086                    }
9087                    self.write(")");
9088                }
9089                if !with_params.is_empty() {
9090                    self.write_space();
9091                    self.write_keyword("WITH");
9092                    self.write(" (");
9093                    for (i, (key, val)) in with_params.iter().enumerate() {
9094                        if i > 0 {
9095                            self.write(", ");
9096                        }
9097                        self.write(key);
9098                        self.write("=");
9099                        self.write(val);
9100                    }
9101                    self.write(")");
9102                }
9103                if let Some(ref tablespace) = using_index_tablespace {
9104                    self.write_space();
9105                    self.write_keyword("USING INDEX TABLESPACE");
9106                    self.write_space();
9107                    self.write(tablespace);
9108                }
9109                if let Some(ref where_expr) = where_clause {
9110                    self.write_space();
9111                    self.write_keyword("WHERE");
9112                    self.write(" (");
9113                    self.generate_expression(where_expr)?;
9114                    self.write(")");
9115                }
9116            }
9117            TableConstraint::Tags(tags) => {
9118                self.write_keyword("TAG");
9119                self.write(" (");
9120                for (i, expr) in tags.expressions.iter().enumerate() {
9121                    if i > 0 {
9122                        self.write(", ");
9123                    }
9124                    self.generate_expression(expr)?;
9125                }
9126                self.write(")");
9127            }
9128            TableConstraint::InitiallyDeferred { deferred } => {
9129                self.write_keyword("INITIALLY");
9130                self.write_space();
9131                if *deferred {
9132                    self.write_keyword("DEFERRED");
9133                } else {
9134                    self.write_keyword("IMMEDIATE");
9135                }
9136            }
9137        }
9138        Ok(())
9139    }
9140
9141    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9142        // Output USING BTREE/HASH (MySQL) - comes first
9143        if let Some(using) = &modifiers.using {
9144            self.write_space();
9145            self.write_keyword("USING");
9146            self.write_space();
9147            self.write_keyword(using);
9148        }
9149        // Output ENFORCED/NOT ENFORCED
9150        if let Some(enforced) = modifiers.enforced {
9151            self.write_space();
9152            if enforced {
9153                self.write_keyword("ENFORCED");
9154            } else {
9155                self.write_keyword("NOT ENFORCED");
9156            }
9157        }
9158        // Output DEFERRABLE/NOT DEFERRABLE
9159        if let Some(deferrable) = modifiers.deferrable {
9160            self.write_space();
9161            if deferrable {
9162                self.write_keyword("DEFERRABLE");
9163            } else {
9164                self.write_keyword("NOT DEFERRABLE");
9165            }
9166        }
9167        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9168        if let Some(initially_deferred) = modifiers.initially_deferred {
9169            self.write_space();
9170            if initially_deferred {
9171                self.write_keyword("INITIALLY DEFERRED");
9172            } else {
9173                self.write_keyword("INITIALLY IMMEDIATE");
9174            }
9175        }
9176        // Output NORELY
9177        if modifiers.norely {
9178            self.write_space();
9179            self.write_keyword("NORELY");
9180        }
9181        // Output RELY
9182        if modifiers.rely {
9183            self.write_space();
9184            self.write_keyword("RELY");
9185        }
9186        // Output NOT VALID (PostgreSQL)
9187        if modifiers.not_valid {
9188            self.write_space();
9189            self.write_keyword("NOT VALID");
9190        }
9191        // Output ON CONFLICT (SQLite)
9192        if let Some(on_conflict) = &modifiers.on_conflict {
9193            self.write_space();
9194            self.write_keyword("ON CONFLICT");
9195            self.write_space();
9196            self.write_keyword(on_conflict);
9197        }
9198        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9199        if !modifiers.with_options.is_empty() {
9200            self.write_space();
9201            self.write_keyword("WITH");
9202            self.write(" (");
9203            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9204                if i > 0 {
9205                    self.write(", ");
9206                }
9207                self.write(key);
9208                self.write("=");
9209                self.write(value);
9210            }
9211            self.write(")");
9212        }
9213        // Output TSQL ON filegroup
9214        if let Some(ref fg) = modifiers.on_filegroup {
9215            self.write_space();
9216            self.write_keyword("ON");
9217            self.write_space();
9218            let _ = self.generate_identifier(fg);
9219        }
9220    }
9221
9222    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9223    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9224        // Output ENFORCED/NOT ENFORCED
9225        if let Some(enforced) = modifiers.enforced {
9226            self.write_space();
9227            if enforced {
9228                self.write_keyword("ENFORCED");
9229            } else {
9230                self.write_keyword("NOT ENFORCED");
9231            }
9232        }
9233        // Output DEFERRABLE/NOT DEFERRABLE
9234        if let Some(deferrable) = modifiers.deferrable {
9235            self.write_space();
9236            if deferrable {
9237                self.write_keyword("DEFERRABLE");
9238            } else {
9239                self.write_keyword("NOT DEFERRABLE");
9240            }
9241        }
9242        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9243        if let Some(initially_deferred) = modifiers.initially_deferred {
9244            self.write_space();
9245            if initially_deferred {
9246                self.write_keyword("INITIALLY DEFERRED");
9247            } else {
9248                self.write_keyword("INITIALLY IMMEDIATE");
9249            }
9250        }
9251        // Output NORELY
9252        if modifiers.norely {
9253            self.write_space();
9254            self.write_keyword("NORELY");
9255        }
9256        // Output RELY
9257        if modifiers.rely {
9258            self.write_space();
9259            self.write_keyword("RELY");
9260        }
9261        // Output NOT VALID (PostgreSQL)
9262        if modifiers.not_valid {
9263            self.write_space();
9264            self.write_keyword("NOT VALID");
9265        }
9266        // Output ON CONFLICT (SQLite)
9267        if let Some(on_conflict) = &modifiers.on_conflict {
9268            self.write_space();
9269            self.write_keyword("ON CONFLICT");
9270            self.write_space();
9271            self.write_keyword(on_conflict);
9272        }
9273        // Output MySQL index-specific modifiers
9274        self.generate_index_specific_modifiers(modifiers);
9275    }
9276
9277    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9278    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9279        if let Some(ref comment) = modifiers.comment {
9280            self.write_space();
9281            self.write_keyword("COMMENT");
9282            self.write(" '");
9283            self.write(comment);
9284            self.write("'");
9285        }
9286        if let Some(visible) = modifiers.visible {
9287            self.write_space();
9288            if visible {
9289                self.write_keyword("VISIBLE");
9290            } else {
9291                self.write_keyword("INVISIBLE");
9292            }
9293        }
9294        if let Some(ref attr) = modifiers.engine_attribute {
9295            self.write_space();
9296            self.write_keyword("ENGINE_ATTRIBUTE");
9297            self.write(" = '");
9298            self.write(attr);
9299            self.write("'");
9300        }
9301        if let Some(ref parser) = modifiers.with_parser {
9302            self.write_space();
9303            self.write_keyword("WITH PARSER");
9304            self.write_space();
9305            self.write(parser);
9306        }
9307    }
9308
9309    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9310        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9311        if !fk_ref.match_after_actions {
9312            if let Some(ref match_type) = fk_ref.match_type {
9313                self.write_space();
9314                self.write_keyword("MATCH");
9315                self.write_space();
9316                match match_type {
9317                    MatchType::Full => self.write_keyword("FULL"),
9318                    MatchType::Partial => self.write_keyword("PARTIAL"),
9319                    MatchType::Simple => self.write_keyword("SIMPLE"),
9320                }
9321            }
9322        }
9323
9324        // Output ON UPDATE and ON DELETE in the original order
9325        if fk_ref.on_update_first {
9326            if let Some(ref action) = fk_ref.on_update {
9327                self.write_space();
9328                self.write_keyword("ON UPDATE");
9329                self.write_space();
9330                self.generate_referential_action(action);
9331            }
9332            if let Some(ref action) = fk_ref.on_delete {
9333                self.write_space();
9334                self.write_keyword("ON DELETE");
9335                self.write_space();
9336                self.generate_referential_action(action);
9337            }
9338        } else {
9339            if let Some(ref action) = fk_ref.on_delete {
9340                self.write_space();
9341                self.write_keyword("ON DELETE");
9342                self.write_space();
9343                self.generate_referential_action(action);
9344            }
9345            if let Some(ref action) = fk_ref.on_update {
9346                self.write_space();
9347                self.write_keyword("ON UPDATE");
9348                self.write_space();
9349                self.generate_referential_action(action);
9350            }
9351        }
9352
9353        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9354        if fk_ref.match_after_actions {
9355            if let Some(ref match_type) = fk_ref.match_type {
9356                self.write_space();
9357                self.write_keyword("MATCH");
9358                self.write_space();
9359                match match_type {
9360                    MatchType::Full => self.write_keyword("FULL"),
9361                    MatchType::Partial => self.write_keyword("PARTIAL"),
9362                    MatchType::Simple => self.write_keyword("SIMPLE"),
9363                }
9364            }
9365        }
9366
9367        // DEFERRABLE / NOT DEFERRABLE
9368        if let Some(deferrable) = fk_ref.deferrable {
9369            self.write_space();
9370            if deferrable {
9371                self.write_keyword("DEFERRABLE");
9372            } else {
9373                self.write_keyword("NOT DEFERRABLE");
9374            }
9375        }
9376
9377        Ok(())
9378    }
9379
9380    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9381        match action {
9382            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9383            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9384            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9385            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9386            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9387        }
9388    }
9389
9390    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9391        // Athena: DROP TABLE uses Hive engine (backticks)
9392        let saved_athena_hive_context = self.athena_hive_context;
9393        if matches!(
9394            self.config.dialect,
9395            Some(crate::dialects::DialectType::Athena)
9396        ) {
9397            self.athena_hive_context = true;
9398        }
9399
9400        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9401        for comment in &dt.leading_comments {
9402            self.write_formatted_comment(comment);
9403            self.write_space();
9404        }
9405        self.write_keyword("DROP TABLE");
9406
9407        if dt.if_exists {
9408            self.write_space();
9409            self.write_keyword("IF EXISTS");
9410        }
9411
9412        self.write_space();
9413        for (i, table) in dt.names.iter().enumerate() {
9414            if i > 0 {
9415                self.write(", ");
9416            }
9417            self.generate_table(table)?;
9418        }
9419
9420        if dt.cascade_constraints {
9421            self.write_space();
9422            self.write_keyword("CASCADE CONSTRAINTS");
9423        } else if dt.cascade {
9424            self.write_space();
9425            self.write_keyword("CASCADE");
9426        }
9427
9428        if dt.purge {
9429            self.write_space();
9430            self.write_keyword("PURGE");
9431        }
9432
9433        // Restore Athena Hive context
9434        self.athena_hive_context = saved_athena_hive_context;
9435
9436        Ok(())
9437    }
9438
9439    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9440        // Athena: ALTER TABLE uses Hive engine (backticks)
9441        let saved_athena_hive_context = self.athena_hive_context;
9442        if matches!(
9443            self.config.dialect,
9444            Some(crate::dialects::DialectType::Athena)
9445        ) {
9446            self.athena_hive_context = true;
9447        }
9448
9449        self.write_keyword("ALTER TABLE");
9450        if at.if_exists {
9451            self.write_space();
9452            self.write_keyword("IF EXISTS");
9453        }
9454        self.write_space();
9455        self.generate_table(&at.name)?;
9456
9457        // ClickHouse: ON CLUSTER clause
9458        if let Some(ref on_cluster) = at.on_cluster {
9459            self.write_space();
9460            self.generate_on_cluster(on_cluster)?;
9461        }
9462
9463        // Hive: PARTITION(key=value, ...) clause
9464        if let Some(ref partition) = at.partition {
9465            self.write_space();
9466            self.write_keyword("PARTITION");
9467            self.write("(");
9468            for (i, (key, value)) in partition.iter().enumerate() {
9469                if i > 0 {
9470                    self.write(", ");
9471                }
9472                self.generate_identifier(key)?;
9473                self.write(" = ");
9474                self.generate_expression(value)?;
9475            }
9476            self.write(")");
9477        }
9478
9479        // TSQL: WITH CHECK / WITH NOCHECK modifier
9480        if let Some(ref with_check) = at.with_check {
9481            self.write_space();
9482            self.write_keyword(with_check);
9483        }
9484
9485        if self.config.pretty {
9486            // In pretty mode, format actions with newlines and indentation
9487            self.write_newline();
9488            self.indent_level += 1;
9489            for (i, action) in at.actions.iter().enumerate() {
9490                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9491                let is_continuation = i > 0
9492                    && matches!(
9493                        (&at.actions[i - 1], action),
9494                        (
9495                            AlterTableAction::AddColumn { .. },
9496                            AlterTableAction::AddColumn { .. }
9497                        ) | (
9498                            AlterTableAction::AddConstraint(_),
9499                            AlterTableAction::AddConstraint(_)
9500                        )
9501                    );
9502                if i > 0 {
9503                    self.write(",");
9504                    self.write_newline();
9505                }
9506                self.write_indent();
9507                self.generate_alter_action_with_continuation(action, is_continuation)?;
9508            }
9509            self.indent_level -= 1;
9510        } else {
9511            for (i, action) in at.actions.iter().enumerate() {
9512                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9513                let is_continuation = i > 0
9514                    && matches!(
9515                        (&at.actions[i - 1], action),
9516                        (
9517                            AlterTableAction::AddColumn { .. },
9518                            AlterTableAction::AddColumn { .. }
9519                        ) | (
9520                            AlterTableAction::AddConstraint(_),
9521                            AlterTableAction::AddConstraint(_)
9522                        )
9523                    );
9524                if i > 0 {
9525                    self.write(",");
9526                }
9527                self.write_space();
9528                self.generate_alter_action_with_continuation(action, is_continuation)?;
9529            }
9530        }
9531
9532        // MySQL ALTER TABLE trailing options
9533        if let Some(ref algorithm) = at.algorithm {
9534            self.write(", ");
9535            self.write_keyword("ALGORITHM");
9536            self.write("=");
9537            self.write_keyword(algorithm);
9538        }
9539        if let Some(ref lock) = at.lock {
9540            self.write(", ");
9541            self.write_keyword("LOCK");
9542            self.write("=");
9543            self.write_keyword(lock);
9544        }
9545
9546        // Restore Athena Hive context
9547        self.athena_hive_context = saved_athena_hive_context;
9548
9549        Ok(())
9550    }
9551
9552    fn generate_alter_action_with_continuation(
9553        &mut self,
9554        action: &AlterTableAction,
9555        is_continuation: bool,
9556    ) -> Result<()> {
9557        match action {
9558            AlterTableAction::AddColumn {
9559                column,
9560                if_not_exists,
9561                position,
9562            } => {
9563                use crate::dialects::DialectType;
9564                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9565                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9566                // For other dialects, repeat ADD COLUMN for each
9567                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9568                let is_tsql_like = matches!(
9569                    self.config.dialect,
9570                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9571                );
9572                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9573                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9574
9575                if is_continuation && (is_snowflake || is_tsql_like) {
9576                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9577                } else if is_snowflake {
9578                    self.write_keyword("ADD");
9579                    self.write_space();
9580                } else if is_athena {
9581                    // Athena uses ADD COLUMNS (col_def) syntax
9582                    self.write_keyword("ADD COLUMNS");
9583                    self.write(" (");
9584                } else if self.config.alter_table_include_column_keyword {
9585                    self.write_keyword("ADD COLUMN");
9586                    self.write_space();
9587                } else {
9588                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9589                    self.write_keyword("ADD");
9590                    self.write_space();
9591                }
9592
9593                if *if_not_exists {
9594                    self.write_keyword("IF NOT EXISTS");
9595                    self.write_space();
9596                }
9597                self.generate_column_def(column)?;
9598
9599                // Close parenthesis for Athena
9600                if is_athena {
9601                    self.write(")");
9602                }
9603
9604                // Column position (FIRST or AFTER)
9605                if let Some(pos) = position {
9606                    self.write_space();
9607                    match pos {
9608                        ColumnPosition::First => self.write_keyword("FIRST"),
9609                        ColumnPosition::After(col_name) => {
9610                            self.write_keyword("AFTER");
9611                            self.write_space();
9612                            self.generate_identifier(col_name)?;
9613                        }
9614                    }
9615                }
9616            }
9617            AlterTableAction::DropColumn {
9618                name,
9619                if_exists,
9620                cascade,
9621            } => {
9622                self.write_keyword("DROP COLUMN");
9623                if *if_exists {
9624                    self.write_space();
9625                    self.write_keyword("IF EXISTS");
9626                }
9627                self.write_space();
9628                self.generate_identifier(name)?;
9629                if *cascade {
9630                    self.write_space();
9631                    self.write_keyword("CASCADE");
9632                }
9633            }
9634            AlterTableAction::DropColumns { names } => {
9635                self.write_keyword("DROP COLUMNS");
9636                self.write(" (");
9637                for (i, name) in names.iter().enumerate() {
9638                    if i > 0 {
9639                        self.write(", ");
9640                    }
9641                    self.generate_identifier(name)?;
9642                }
9643                self.write(")");
9644            }
9645            AlterTableAction::RenameColumn {
9646                old_name,
9647                new_name,
9648                if_exists,
9649            } => {
9650                self.write_keyword("RENAME COLUMN");
9651                if *if_exists {
9652                    self.write_space();
9653                    self.write_keyword("IF EXISTS");
9654                }
9655                self.write_space();
9656                self.generate_identifier(old_name)?;
9657                self.write_space();
9658                self.write_keyword("TO");
9659                self.write_space();
9660                self.generate_identifier(new_name)?;
9661            }
9662            AlterTableAction::AlterColumn {
9663                name,
9664                action,
9665                use_modify_keyword,
9666            } => {
9667                use crate::dialects::DialectType;
9668                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9669                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9670                let use_modify = *use_modify_keyword
9671                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9672                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9673                if use_modify {
9674                    self.write_keyword("MODIFY COLUMN");
9675                    self.write_space();
9676                    self.generate_identifier(name)?;
9677                    // For MODIFY COLUMN, output the type directly
9678                    if let AlterColumnAction::SetDataType {
9679                        data_type,
9680                        using: _,
9681                        collate,
9682                    } = action
9683                    {
9684                        self.write_space();
9685                        self.generate_data_type(data_type)?;
9686                        // Output COLLATE clause if present
9687                        if let Some(collate_name) = collate {
9688                            self.write_space();
9689                            self.write_keyword("COLLATE");
9690                            self.write_space();
9691                            // Output as single-quoted string
9692                            self.write(&format!("'{}'", collate_name));
9693                        }
9694                    } else {
9695                        self.write_space();
9696                        self.generate_alter_column_action(action)?;
9697                    }
9698                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9699                    && matches!(action, AlterColumnAction::SetDataType { .. })
9700                {
9701                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9702                    self.write_keyword("CHANGE COLUMN");
9703                    self.write_space();
9704                    self.generate_identifier(name)?;
9705                    self.write_space();
9706                    self.generate_identifier(name)?;
9707                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9708                        self.write_space();
9709                        self.generate_data_type(data_type)?;
9710                    }
9711                } else {
9712                    self.write_keyword("ALTER COLUMN");
9713                    self.write_space();
9714                    self.generate_identifier(name)?;
9715                    self.write_space();
9716                    self.generate_alter_column_action(action)?;
9717                }
9718            }
9719            AlterTableAction::RenameTable(new_name) => {
9720                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9721                let mysql_like = matches!(
9722                    self.config.dialect,
9723                    Some(DialectType::MySQL)
9724                        | Some(DialectType::Doris)
9725                        | Some(DialectType::StarRocks)
9726                        | Some(DialectType::SingleStore)
9727                );
9728                if mysql_like {
9729                    self.write_keyword("RENAME");
9730                } else {
9731                    self.write_keyword("RENAME TO");
9732                }
9733                self.write_space();
9734                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9735                let rename_table_with_db = !matches!(
9736                    self.config.dialect,
9737                    Some(DialectType::Doris)
9738                        | Some(DialectType::DuckDB)
9739                        | Some(DialectType::BigQuery)
9740                        | Some(DialectType::PostgreSQL)
9741                );
9742                if !rename_table_with_db {
9743                    let mut stripped = new_name.clone();
9744                    stripped.schema = None;
9745                    stripped.catalog = None;
9746                    self.generate_table(&stripped)?;
9747                } else {
9748                    self.generate_table(new_name)?;
9749                }
9750            }
9751            AlterTableAction::AddConstraint(constraint) => {
9752                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9753                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9754                if !is_continuation {
9755                    self.write_keyword("ADD");
9756                    self.write_space();
9757                }
9758                self.generate_table_constraint(constraint)?;
9759            }
9760            AlterTableAction::DropConstraint { name, if_exists } => {
9761                self.write_keyword("DROP CONSTRAINT");
9762                if *if_exists {
9763                    self.write_space();
9764                    self.write_keyword("IF EXISTS");
9765                }
9766                self.write_space();
9767                self.generate_identifier(name)?;
9768            }
9769            AlterTableAction::DropForeignKey { name } => {
9770                self.write_keyword("DROP FOREIGN KEY");
9771                self.write_space();
9772                self.generate_identifier(name)?;
9773            }
9774            AlterTableAction::DropPartition {
9775                partitions,
9776                if_exists,
9777            } => {
9778                self.write_keyword("DROP");
9779                if *if_exists {
9780                    self.write_space();
9781                    self.write_keyword("IF EXISTS");
9782                }
9783                for (i, partition) in partitions.iter().enumerate() {
9784                    if i > 0 {
9785                        self.write(",");
9786                    }
9787                    self.write_space();
9788                    self.write_keyword("PARTITION");
9789                    // Check for special ClickHouse partition formats
9790                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9791                        // ClickHouse: PARTITION <expression>
9792                        self.write_space();
9793                        self.generate_expression(&partition[0].1)?;
9794                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9795                        // ClickHouse: PARTITION ALL
9796                        self.write_space();
9797                        self.write_keyword("ALL");
9798                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9799                        // ClickHouse: PARTITION ID 'string'
9800                        self.write_space();
9801                        self.write_keyword("ID");
9802                        self.write_space();
9803                        self.generate_expression(&partition[0].1)?;
9804                    } else {
9805                        // Standard SQL: PARTITION(key=value, ...)
9806                        self.write("(");
9807                        for (j, (key, value)) in partition.iter().enumerate() {
9808                            if j > 0 {
9809                                self.write(", ");
9810                            }
9811                            self.generate_identifier(key)?;
9812                            self.write(" = ");
9813                            self.generate_expression(value)?;
9814                        }
9815                        self.write(")");
9816                    }
9817                }
9818            }
9819            AlterTableAction::Delete { where_clause } => {
9820                self.write_keyword("DELETE");
9821                self.write_space();
9822                self.write_keyword("WHERE");
9823                self.write_space();
9824                self.generate_expression(where_clause)?;
9825            }
9826            AlterTableAction::SwapWith(target) => {
9827                self.write_keyword("SWAP WITH");
9828                self.write_space();
9829                self.generate_table(target)?;
9830            }
9831            AlterTableAction::SetProperty { properties } => {
9832                use crate::dialects::DialectType;
9833                self.write_keyword("SET");
9834                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9835                let is_trino_presto = matches!(
9836                    self.config.dialect,
9837                    Some(DialectType::Trino) | Some(DialectType::Presto)
9838                );
9839                if is_trino_presto {
9840                    self.write_space();
9841                    self.write_keyword("PROPERTIES");
9842                }
9843                let eq = if is_trino_presto { " = " } else { "=" };
9844                for (i, (key, value)) in properties.iter().enumerate() {
9845                    if i > 0 {
9846                        self.write(",");
9847                    }
9848                    self.write_space();
9849                    // Handle quoted property names for Trino
9850                    if key.contains(' ') {
9851                        self.generate_string_literal(key)?;
9852                    } else {
9853                        self.write(key);
9854                    }
9855                    self.write(eq);
9856                    self.generate_expression(value)?;
9857                }
9858            }
9859            AlterTableAction::UnsetProperty { properties } => {
9860                self.write_keyword("UNSET");
9861                for (i, name) in properties.iter().enumerate() {
9862                    if i > 0 {
9863                        self.write(",");
9864                    }
9865                    self.write_space();
9866                    self.write(name);
9867                }
9868            }
9869            AlterTableAction::ClusterBy { expressions } => {
9870                self.write_keyword("CLUSTER BY");
9871                self.write(" (");
9872                for (i, expr) in expressions.iter().enumerate() {
9873                    if i > 0 {
9874                        self.write(", ");
9875                    }
9876                    self.generate_expression(expr)?;
9877                }
9878                self.write(")");
9879            }
9880            AlterTableAction::SetTag { expressions } => {
9881                self.write_keyword("SET TAG");
9882                for (i, (key, value)) in expressions.iter().enumerate() {
9883                    if i > 0 {
9884                        self.write(",");
9885                    }
9886                    self.write_space();
9887                    self.write(key);
9888                    self.write(" = ");
9889                    self.generate_expression(value)?;
9890                }
9891            }
9892            AlterTableAction::UnsetTag { names } => {
9893                self.write_keyword("UNSET TAG");
9894                for (i, name) in names.iter().enumerate() {
9895                    if i > 0 {
9896                        self.write(",");
9897                    }
9898                    self.write_space();
9899                    self.write(name);
9900                }
9901            }
9902            AlterTableAction::SetOptions { expressions } => {
9903                self.write_keyword("SET");
9904                self.write(" (");
9905                for (i, expr) in expressions.iter().enumerate() {
9906                    if i > 0 {
9907                        self.write(", ");
9908                    }
9909                    self.generate_expression(expr)?;
9910                }
9911                self.write(")");
9912            }
9913            AlterTableAction::AlterIndex { name, visible } => {
9914                self.write_keyword("ALTER INDEX");
9915                self.write_space();
9916                self.generate_identifier(name)?;
9917                self.write_space();
9918                if *visible {
9919                    self.write_keyword("VISIBLE");
9920                } else {
9921                    self.write_keyword("INVISIBLE");
9922                }
9923            }
9924            AlterTableAction::SetAttribute { attribute } => {
9925                self.write_keyword("SET");
9926                self.write_space();
9927                self.write_keyword(attribute);
9928            }
9929            AlterTableAction::SetStageFileFormat { options } => {
9930                self.write_keyword("SET");
9931                self.write_space();
9932                self.write_keyword("STAGE_FILE_FORMAT");
9933                self.write(" = (");
9934                if let Some(opts) = options {
9935                    self.generate_space_separated_properties(opts)?;
9936                }
9937                self.write(")");
9938            }
9939            AlterTableAction::SetStageCopyOptions { options } => {
9940                self.write_keyword("SET");
9941                self.write_space();
9942                self.write_keyword("STAGE_COPY_OPTIONS");
9943                self.write(" = (");
9944                if let Some(opts) = options {
9945                    self.generate_space_separated_properties(opts)?;
9946                }
9947                self.write(")");
9948            }
9949            AlterTableAction::AddColumns { columns, cascade } => {
9950                // Oracle uses ADD (...) without COLUMNS keyword
9951                // Hive/Spark uses ADD COLUMNS (...)
9952                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9953                if is_oracle {
9954                    self.write_keyword("ADD");
9955                } else {
9956                    self.write_keyword("ADD COLUMNS");
9957                }
9958                self.write(" (");
9959                for (i, col) in columns.iter().enumerate() {
9960                    if i > 0 {
9961                        self.write(", ");
9962                    }
9963                    self.generate_column_def(col)?;
9964                }
9965                self.write(")");
9966                if *cascade {
9967                    self.write_space();
9968                    self.write_keyword("CASCADE");
9969                }
9970            }
9971            AlterTableAction::ChangeColumn {
9972                old_name,
9973                new_name,
9974                data_type,
9975                comment,
9976                cascade,
9977            } => {
9978                use crate::dialects::DialectType;
9979                let is_spark = matches!(
9980                    self.config.dialect,
9981                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9982                );
9983                let is_rename = old_name.name != new_name.name;
9984
9985                if is_spark {
9986                    if is_rename {
9987                        // Spark: RENAME COLUMN old TO new
9988                        self.write_keyword("RENAME COLUMN");
9989                        self.write_space();
9990                        self.generate_identifier(old_name)?;
9991                        self.write_space();
9992                        self.write_keyword("TO");
9993                        self.write_space();
9994                        self.generate_identifier(new_name)?;
9995                    } else if comment.is_some() {
9996                        // Spark: ALTER COLUMN old COMMENT 'comment'
9997                        self.write_keyword("ALTER COLUMN");
9998                        self.write_space();
9999                        self.generate_identifier(old_name)?;
10000                        self.write_space();
10001                        self.write_keyword("COMMENT");
10002                        self.write_space();
10003                        self.write("'");
10004                        self.write(comment.as_ref().unwrap());
10005                        self.write("'");
10006                    } else if data_type.is_some() {
10007                        // Spark: ALTER COLUMN old TYPE data_type
10008                        self.write_keyword("ALTER COLUMN");
10009                        self.write_space();
10010                        self.generate_identifier(old_name)?;
10011                        self.write_space();
10012                        self.write_keyword("TYPE");
10013                        self.write_space();
10014                        self.generate_data_type(data_type.as_ref().unwrap())?;
10015                    } else {
10016                        // Fallback to CHANGE COLUMN
10017                        self.write_keyword("CHANGE COLUMN");
10018                        self.write_space();
10019                        self.generate_identifier(old_name)?;
10020                        self.write_space();
10021                        self.generate_identifier(new_name)?;
10022                    }
10023                } else {
10024                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10025                    if data_type.is_some() {
10026                        self.write_keyword("CHANGE COLUMN");
10027                    } else {
10028                        self.write_keyword("CHANGE");
10029                    }
10030                    self.write_space();
10031                    self.generate_identifier(old_name)?;
10032                    self.write_space();
10033                    self.generate_identifier(new_name)?;
10034                    if let Some(ref dt) = data_type {
10035                        self.write_space();
10036                        self.generate_data_type(dt)?;
10037                    }
10038                    if let Some(ref c) = comment {
10039                        self.write_space();
10040                        self.write_keyword("COMMENT");
10041                        self.write_space();
10042                        self.write("'");
10043                        self.write(c);
10044                        self.write("'");
10045                    }
10046                    if *cascade {
10047                        self.write_space();
10048                        self.write_keyword("CASCADE");
10049                    }
10050                }
10051            }
10052            AlterTableAction::AddPartition {
10053                partition,
10054                if_not_exists,
10055                location,
10056            } => {
10057                self.write_keyword("ADD");
10058                self.write_space();
10059                if *if_not_exists {
10060                    self.write_keyword("IF NOT EXISTS");
10061                    self.write_space();
10062                }
10063                self.generate_expression(partition)?;
10064                if let Some(ref loc) = location {
10065                    self.write_space();
10066                    self.write_keyword("LOCATION");
10067                    self.write_space();
10068                    self.generate_expression(loc)?;
10069                }
10070            }
10071            AlterTableAction::AlterSortKey {
10072                this,
10073                expressions,
10074                compound,
10075            } => {
10076                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10077                self.write_keyword("ALTER");
10078                if *compound {
10079                    self.write_space();
10080                    self.write_keyword("COMPOUND");
10081                }
10082                self.write_space();
10083                self.write_keyword("SORTKEY");
10084                self.write_space();
10085                if let Some(style) = this {
10086                    self.write_keyword(style);
10087                } else if !expressions.is_empty() {
10088                    self.write("(");
10089                    for (i, expr) in expressions.iter().enumerate() {
10090                        if i > 0 {
10091                            self.write(", ");
10092                        }
10093                        self.generate_expression(expr)?;
10094                    }
10095                    self.write(")");
10096                }
10097            }
10098            AlterTableAction::AlterDistStyle { style, distkey } => {
10099                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10100                self.write_keyword("ALTER");
10101                self.write_space();
10102                self.write_keyword("DISTSTYLE");
10103                self.write_space();
10104                self.write_keyword(style);
10105                if let Some(col) = distkey {
10106                    self.write_space();
10107                    self.write_keyword("DISTKEY");
10108                    self.write_space();
10109                    self.generate_identifier(col)?;
10110                }
10111            }
10112            AlterTableAction::SetTableProperties { properties } => {
10113                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10114                self.write_keyword("SET TABLE PROPERTIES");
10115                self.write(" (");
10116                for (i, (key, value)) in properties.iter().enumerate() {
10117                    if i > 0 {
10118                        self.write(", ");
10119                    }
10120                    self.generate_expression(key)?;
10121                    self.write(" = ");
10122                    self.generate_expression(value)?;
10123                }
10124                self.write(")");
10125            }
10126            AlterTableAction::SetLocation { location } => {
10127                // Redshift: SET LOCATION 's3://bucket/folder/'
10128                self.write_keyword("SET LOCATION");
10129                self.write_space();
10130                self.write("'");
10131                self.write(location);
10132                self.write("'");
10133            }
10134            AlterTableAction::SetFileFormat { format } => {
10135                // Redshift: SET FILE FORMAT AVRO
10136                self.write_keyword("SET FILE FORMAT");
10137                self.write_space();
10138                self.write_keyword(format);
10139            }
10140            AlterTableAction::ReplacePartition { partition, source } => {
10141                // ClickHouse: REPLACE PARTITION expr FROM source
10142                self.write_keyword("REPLACE PARTITION");
10143                self.write_space();
10144                self.generate_expression(partition)?;
10145                if let Some(src) = source {
10146                    self.write_space();
10147                    self.write_keyword("FROM");
10148                    self.write_space();
10149                    self.generate_expression(src)?;
10150                }
10151            }
10152            AlterTableAction::Raw { sql } => {
10153                self.write(sql);
10154            }
10155        }
10156        Ok(())
10157    }
10158
10159    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10160        match action {
10161            AlterColumnAction::SetDataType {
10162                data_type,
10163                using,
10164                collate,
10165            } => {
10166                use crate::dialects::DialectType;
10167                // Dialect-specific type change syntax:
10168                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10169                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10170                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10171                let is_no_prefix = matches!(
10172                    self.config.dialect,
10173                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10174                );
10175                let is_type_only = matches!(
10176                    self.config.dialect,
10177                    Some(DialectType::Redshift)
10178                        | Some(DialectType::Spark)
10179                        | Some(DialectType::Databricks)
10180                );
10181                if is_type_only {
10182                    self.write_keyword("TYPE");
10183                    self.write_space();
10184                } else if !is_no_prefix {
10185                    self.write_keyword("SET DATA TYPE");
10186                    self.write_space();
10187                }
10188                self.generate_data_type(data_type)?;
10189                if let Some(ref collation) = collate {
10190                    self.write_space();
10191                    self.write_keyword("COLLATE");
10192                    self.write_space();
10193                    self.write(collation);
10194                }
10195                if let Some(ref using_expr) = using {
10196                    self.write_space();
10197                    self.write_keyword("USING");
10198                    self.write_space();
10199                    self.generate_expression(using_expr)?;
10200                }
10201            }
10202            AlterColumnAction::SetDefault(expr) => {
10203                self.write_keyword("SET DEFAULT");
10204                self.write_space();
10205                self.generate_expression(expr)?;
10206            }
10207            AlterColumnAction::DropDefault => {
10208                self.write_keyword("DROP DEFAULT");
10209            }
10210            AlterColumnAction::SetNotNull => {
10211                self.write_keyword("SET NOT NULL");
10212            }
10213            AlterColumnAction::DropNotNull => {
10214                self.write_keyword("DROP NOT NULL");
10215            }
10216            AlterColumnAction::Comment(comment) => {
10217                self.write_keyword("COMMENT");
10218                self.write_space();
10219                self.generate_string_literal(comment)?;
10220            }
10221            AlterColumnAction::SetVisible => {
10222                self.write_keyword("SET VISIBLE");
10223            }
10224            AlterColumnAction::SetInvisible => {
10225                self.write_keyword("SET INVISIBLE");
10226            }
10227        }
10228        Ok(())
10229    }
10230
10231    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10232        self.write_keyword("CREATE");
10233
10234        if ci.unique {
10235            self.write_space();
10236            self.write_keyword("UNIQUE");
10237        }
10238
10239        // TSQL CLUSTERED/NONCLUSTERED modifier
10240        if let Some(ref clustered) = ci.clustered {
10241            self.write_space();
10242            self.write_keyword(clustered);
10243        }
10244
10245        self.write_space();
10246        self.write_keyword("INDEX");
10247
10248        // PostgreSQL CONCURRENTLY modifier
10249        if ci.concurrently {
10250            self.write_space();
10251            self.write_keyword("CONCURRENTLY");
10252        }
10253
10254        if ci.if_not_exists {
10255            self.write_space();
10256            self.write_keyword("IF NOT EXISTS");
10257        }
10258
10259        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10260        if !ci.name.name.is_empty() {
10261            self.write_space();
10262            self.generate_identifier(&ci.name)?;
10263        }
10264        self.write_space();
10265        self.write_keyword("ON");
10266        // Hive uses ON TABLE
10267        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10268            self.write_space();
10269            self.write_keyword("TABLE");
10270        }
10271        self.write_space();
10272        self.generate_table(&ci.table)?;
10273
10274        // Column list (optional for COLUMNSTORE indexes)
10275        // Standard SQL convention: ON t(a) without space before paren
10276        if !ci.columns.is_empty() || ci.using.is_some() {
10277            let space_before_paren = false;
10278
10279            if let Some(ref using) = ci.using {
10280                self.write_space();
10281                self.write_keyword("USING");
10282                self.write_space();
10283                self.write(using);
10284                if space_before_paren {
10285                    self.write(" (");
10286                } else {
10287                    self.write("(");
10288                }
10289            } else {
10290                if space_before_paren {
10291                    self.write(" (");
10292                } else {
10293                    self.write("(");
10294                }
10295            }
10296            for (i, col) in ci.columns.iter().enumerate() {
10297                if i > 0 {
10298                    self.write(", ");
10299                }
10300                self.generate_identifier(&col.column)?;
10301                if let Some(ref opclass) = col.opclass {
10302                    self.write_space();
10303                    self.write(opclass);
10304                }
10305                if col.desc {
10306                    self.write_space();
10307                    self.write_keyword("DESC");
10308                } else if col.asc {
10309                    self.write_space();
10310                    self.write_keyword("ASC");
10311                }
10312                if let Some(nulls_first) = col.nulls_first {
10313                    self.write_space();
10314                    self.write_keyword("NULLS");
10315                    self.write_space();
10316                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10317                }
10318            }
10319            self.write(")");
10320        }
10321
10322        // PostgreSQL INCLUDE (col1, col2) clause
10323        if !ci.include_columns.is_empty() {
10324            self.write_space();
10325            self.write_keyword("INCLUDE");
10326            self.write(" (");
10327            for (i, col) in ci.include_columns.iter().enumerate() {
10328                if i > 0 {
10329                    self.write(", ");
10330                }
10331                self.generate_identifier(col)?;
10332            }
10333            self.write(")");
10334        }
10335
10336        // TSQL: WITH (option=value, ...) clause
10337        if !ci.with_options.is_empty() {
10338            self.write_space();
10339            self.write_keyword("WITH");
10340            self.write(" (");
10341            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10342                if i > 0 {
10343                    self.write(", ");
10344                }
10345                self.write(key);
10346                self.write("=");
10347                self.write(value);
10348            }
10349            self.write(")");
10350        }
10351
10352        // PostgreSQL WHERE clause for partial indexes
10353        if let Some(ref where_clause) = ci.where_clause {
10354            self.write_space();
10355            self.write_keyword("WHERE");
10356            self.write_space();
10357            self.generate_expression(where_clause)?;
10358        }
10359
10360        // TSQL: ON filegroup or partition scheme clause
10361        if let Some(ref on_fg) = ci.on_filegroup {
10362            self.write_space();
10363            self.write_keyword("ON");
10364            self.write_space();
10365            self.write(on_fg);
10366        }
10367
10368        Ok(())
10369    }
10370
10371    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10372        self.write_keyword("DROP INDEX");
10373
10374        if di.concurrently {
10375            self.write_space();
10376            self.write_keyword("CONCURRENTLY");
10377        }
10378
10379        if di.if_exists {
10380            self.write_space();
10381            self.write_keyword("IF EXISTS");
10382        }
10383
10384        self.write_space();
10385        self.generate_identifier(&di.name)?;
10386
10387        if let Some(ref table) = di.table {
10388            self.write_space();
10389            self.write_keyword("ON");
10390            self.write_space();
10391            self.generate_table(table)?;
10392        }
10393
10394        Ok(())
10395    }
10396
10397    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10398        self.write_keyword("CREATE");
10399
10400        // MySQL: ALGORITHM=...
10401        if let Some(ref algorithm) = cv.algorithm {
10402            self.write_space();
10403            self.write_keyword("ALGORITHM");
10404            self.write("=");
10405            self.write_keyword(algorithm);
10406        }
10407
10408        // MySQL: DEFINER=...
10409        if let Some(ref definer) = cv.definer {
10410            self.write_space();
10411            self.write_keyword("DEFINER");
10412            self.write("=");
10413            self.write(definer);
10414        }
10415
10416        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10417        if cv.security_sql_style {
10418            if let Some(ref security) = cv.security {
10419                self.write_space();
10420                self.write_keyword("SQL SECURITY");
10421                self.write_space();
10422                match security {
10423                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10424                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10425                    FunctionSecurity::None => self.write_keyword("NONE"),
10426                }
10427            }
10428        }
10429
10430        if cv.or_replace {
10431            self.write_space();
10432            self.write_keyword("OR REPLACE");
10433        }
10434
10435        if cv.temporary {
10436            self.write_space();
10437            self.write_keyword("TEMPORARY");
10438        }
10439
10440        if cv.materialized {
10441            self.write_space();
10442            self.write_keyword("MATERIALIZED");
10443        }
10444
10445        // Snowflake: SECURE VIEW
10446        if cv.secure {
10447            self.write_space();
10448            self.write_keyword("SECURE");
10449        }
10450
10451        self.write_space();
10452        self.write_keyword("VIEW");
10453
10454        if cv.if_not_exists {
10455            self.write_space();
10456            self.write_keyword("IF NOT EXISTS");
10457        }
10458
10459        self.write_space();
10460        self.generate_table(&cv.name)?;
10461
10462        // ClickHouse: ON CLUSTER clause
10463        if let Some(ref on_cluster) = cv.on_cluster {
10464            self.write_space();
10465            self.generate_on_cluster(on_cluster)?;
10466        }
10467
10468        // ClickHouse: TO destination_table
10469        if let Some(ref to_table) = cv.to_table {
10470            self.write_space();
10471            self.write_keyword("TO");
10472            self.write_space();
10473            self.generate_table(to_table)?;
10474        }
10475
10476        // For regular VIEW: columns come before COPY GRANTS
10477        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10478        if !cv.materialized {
10479            // Regular VIEW: columns first
10480            if !cv.columns.is_empty() {
10481                self.write(" (");
10482                for (i, col) in cv.columns.iter().enumerate() {
10483                    if i > 0 {
10484                        self.write(", ");
10485                    }
10486                    self.generate_identifier(&col.name)?;
10487                    // BigQuery: OPTIONS (key=value, ...) on view column
10488                    if !col.options.is_empty() {
10489                        self.write_space();
10490                        self.generate_options_clause(&col.options)?;
10491                    }
10492                    if let Some(ref comment) = col.comment {
10493                        self.write_space();
10494                        self.write_keyword("COMMENT");
10495                        self.write_space();
10496                        self.generate_string_literal(comment)?;
10497                    }
10498                }
10499                self.write(")");
10500            }
10501
10502            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10503            if !cv.security_sql_style {
10504                if let Some(ref security) = cv.security {
10505                    self.write_space();
10506                    self.write_keyword("SECURITY");
10507                    self.write_space();
10508                    match security {
10509                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10510                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10511                        FunctionSecurity::None => self.write_keyword("NONE"),
10512                    }
10513                }
10514            }
10515
10516            // Snowflake: COPY GRANTS
10517            if cv.copy_grants {
10518                self.write_space();
10519                self.write_keyword("COPY GRANTS");
10520            }
10521        } else {
10522            // MATERIALIZED VIEW: COPY GRANTS first
10523            if cv.copy_grants {
10524                self.write_space();
10525                self.write_keyword("COPY GRANTS");
10526            }
10527
10528            // Doris: If we have a schema (typed columns), generate that instead
10529            if let Some(ref schema) = cv.schema {
10530                self.write(" (");
10531                for (i, expr) in schema.expressions.iter().enumerate() {
10532                    if i > 0 {
10533                        self.write(", ");
10534                    }
10535                    self.generate_expression(expr)?;
10536                }
10537                self.write(")");
10538            } else if !cv.columns.is_empty() {
10539                // Then columns (simple column names without types)
10540                self.write(" (");
10541                for (i, col) in cv.columns.iter().enumerate() {
10542                    if i > 0 {
10543                        self.write(", ");
10544                    }
10545                    self.generate_identifier(&col.name)?;
10546                    // BigQuery: OPTIONS (key=value, ...) on view column
10547                    if !col.options.is_empty() {
10548                        self.write_space();
10549                        self.generate_options_clause(&col.options)?;
10550                    }
10551                    if let Some(ref comment) = col.comment {
10552                        self.write_space();
10553                        self.write_keyword("COMMENT");
10554                        self.write_space();
10555                        self.generate_string_literal(comment)?;
10556                    }
10557                }
10558                self.write(")");
10559            }
10560
10561            // Doris: KEY (columns) for materialized views
10562            if let Some(ref unique_key) = cv.unique_key {
10563                self.write_space();
10564                self.write_keyword("KEY");
10565                self.write(" (");
10566                for (i, expr) in unique_key.expressions.iter().enumerate() {
10567                    if i > 0 {
10568                        self.write(", ");
10569                    }
10570                    self.generate_expression(expr)?;
10571                }
10572                self.write(")");
10573            }
10574        }
10575
10576        // Snowflake: COMMENT = 'text'
10577        if let Some(ref comment) = cv.comment {
10578            self.write_space();
10579            self.write_keyword("COMMENT");
10580            self.write("=");
10581            self.generate_string_literal(comment)?;
10582        }
10583
10584        // Snowflake: TAG (name='value', ...)
10585        if !cv.tags.is_empty() {
10586            self.write_space();
10587            self.write_keyword("TAG");
10588            self.write(" (");
10589            for (i, (name, value)) in cv.tags.iter().enumerate() {
10590                if i > 0 {
10591                    self.write(", ");
10592                }
10593                self.write(name);
10594                self.write("='");
10595                self.write(value);
10596                self.write("'");
10597            }
10598            self.write(")");
10599        }
10600
10601        // BigQuery: OPTIONS (key=value, ...)
10602        if !cv.options.is_empty() {
10603            self.write_space();
10604            self.generate_options_clause(&cv.options)?;
10605        }
10606
10607        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10608        if let Some(ref build) = cv.build {
10609            self.write_space();
10610            self.write_keyword("BUILD");
10611            self.write_space();
10612            self.write_keyword(build);
10613        }
10614
10615        // Doris: REFRESH clause for materialized views
10616        if let Some(ref refresh) = cv.refresh {
10617            self.write_space();
10618            self.generate_refresh_trigger_property(refresh)?;
10619        }
10620
10621        // Redshift: AUTO REFRESH YES|NO for materialized views
10622        if let Some(auto_refresh) = cv.auto_refresh {
10623            self.write_space();
10624            self.write_keyword("AUTO REFRESH");
10625            self.write_space();
10626            if auto_refresh {
10627                self.write_keyword("YES");
10628            } else {
10629                self.write_keyword("NO");
10630            }
10631        }
10632
10633        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10634        for prop in &cv.table_properties {
10635            self.write_space();
10636            self.generate_expression(prop)?;
10637        }
10638
10639        // Only output AS clause if there's a real query (not just NULL placeholder)
10640        if !matches!(&cv.query, Expression::Null(_)) {
10641            self.write_space();
10642            self.write_keyword("AS");
10643            self.write_space();
10644
10645            // Teradata: LOCKING clause (between AS and query)
10646            if let Some(ref mode) = cv.locking_mode {
10647                self.write_keyword("LOCKING");
10648                self.write_space();
10649                self.write_keyword(mode);
10650                if let Some(ref access) = cv.locking_access {
10651                    self.write_space();
10652                    self.write_keyword("FOR");
10653                    self.write_space();
10654                    self.write_keyword(access);
10655                }
10656                self.write_space();
10657            }
10658
10659            if cv.query_parenthesized {
10660                self.write("(");
10661            }
10662            self.generate_expression(&cv.query)?;
10663            if cv.query_parenthesized {
10664                self.write(")");
10665            }
10666        }
10667
10668        // Redshift: WITH NO SCHEMA BINDING (after query)
10669        if cv.no_schema_binding {
10670            self.write_space();
10671            self.write_keyword("WITH NO SCHEMA BINDING");
10672        }
10673
10674        Ok(())
10675    }
10676
10677    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10678        self.write_keyword("DROP");
10679
10680        if dv.materialized {
10681            self.write_space();
10682            self.write_keyword("MATERIALIZED");
10683        }
10684
10685        self.write_space();
10686        self.write_keyword("VIEW");
10687
10688        if dv.if_exists {
10689            self.write_space();
10690            self.write_keyword("IF EXISTS");
10691        }
10692
10693        self.write_space();
10694        self.generate_table(&dv.name)?;
10695
10696        Ok(())
10697    }
10698
10699    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10700        match tr.target {
10701            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10702            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10703        }
10704        if tr.if_exists {
10705            self.write_space();
10706            self.write_keyword("IF EXISTS");
10707        }
10708        self.write_space();
10709        self.generate_table(&tr.table)?;
10710
10711        // ClickHouse: ON CLUSTER clause
10712        if let Some(ref on_cluster) = tr.on_cluster {
10713            self.write_space();
10714            self.generate_on_cluster(on_cluster)?;
10715        }
10716
10717        // Check if first table has a * (multi-table with star)
10718        if !tr.extra_tables.is_empty() {
10719            // Check if the first entry matches the main table (star case)
10720            let skip_first = if let Some(first) = tr.extra_tables.first() {
10721                first.table.name == tr.table.name && first.star
10722            } else {
10723                false
10724            };
10725
10726            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10727            let strip_star = matches!(
10728                self.config.dialect,
10729                Some(crate::dialects::DialectType::PostgreSQL)
10730                    | Some(crate::dialects::DialectType::Redshift)
10731            );
10732            if skip_first && !strip_star {
10733                self.write("*");
10734            }
10735
10736            // Generate additional tables
10737            for (i, entry) in tr.extra_tables.iter().enumerate() {
10738                if i == 0 && skip_first {
10739                    continue; // Already handled the star for first table
10740                }
10741                self.write(", ");
10742                self.generate_table(&entry.table)?;
10743                if entry.star && !strip_star {
10744                    self.write("*");
10745                }
10746            }
10747        }
10748
10749        // RESTART/CONTINUE IDENTITY
10750        if let Some(identity) = &tr.identity {
10751            self.write_space();
10752            match identity {
10753                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10754                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10755            }
10756        }
10757
10758        if tr.cascade {
10759            self.write_space();
10760            self.write_keyword("CASCADE");
10761        }
10762
10763        if tr.restrict {
10764            self.write_space();
10765            self.write_keyword("RESTRICT");
10766        }
10767
10768        // Output Hive PARTITION clause
10769        if let Some(ref partition) = tr.partition {
10770            self.write_space();
10771            self.generate_expression(partition)?;
10772        }
10773
10774        Ok(())
10775    }
10776
10777    fn generate_use(&mut self, u: &Use) -> Result<()> {
10778        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10779        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10780            self.write_keyword("DATABASE");
10781            self.write_space();
10782            self.generate_identifier(&u.this)?;
10783            return Ok(());
10784        }
10785
10786        self.write_keyword("USE");
10787
10788        if let Some(kind) = &u.kind {
10789            self.write_space();
10790            match kind {
10791                UseKind::Database => self.write_keyword("DATABASE"),
10792                UseKind::Schema => self.write_keyword("SCHEMA"),
10793                UseKind::Role => self.write_keyword("ROLE"),
10794                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10795                UseKind::Catalog => self.write_keyword("CATALOG"),
10796                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10797            }
10798        }
10799
10800        self.write_space();
10801        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10802        // without quoting, since these are keywords not identifiers
10803        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10804            self.write(&u.this.name);
10805        } else {
10806            self.generate_identifier(&u.this)?;
10807        }
10808        Ok(())
10809    }
10810
10811    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10812        self.write_keyword("CACHE");
10813        if c.lazy {
10814            self.write_space();
10815            self.write_keyword("LAZY");
10816        }
10817        self.write_space();
10818        self.write_keyword("TABLE");
10819        self.write_space();
10820        self.generate_identifier(&c.table)?;
10821
10822        // OPTIONS clause
10823        if !c.options.is_empty() {
10824            self.write_space();
10825            self.write_keyword("OPTIONS");
10826            self.write("(");
10827            for (i, (key, value)) in c.options.iter().enumerate() {
10828                if i > 0 {
10829                    self.write(", ");
10830                }
10831                self.generate_expression(key)?;
10832                self.write(" = ");
10833                self.generate_expression(value)?;
10834            }
10835            self.write(")");
10836        }
10837
10838        // AS query
10839        if let Some(query) = &c.query {
10840            self.write_space();
10841            self.write_keyword("AS");
10842            self.write_space();
10843            self.generate_expression(query)?;
10844        }
10845
10846        Ok(())
10847    }
10848
10849    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10850        self.write_keyword("UNCACHE TABLE");
10851        if u.if_exists {
10852            self.write_space();
10853            self.write_keyword("IF EXISTS");
10854        }
10855        self.write_space();
10856        self.generate_identifier(&u.table)?;
10857        Ok(())
10858    }
10859
10860    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10861        self.write_keyword("LOAD DATA");
10862        if l.local {
10863            self.write_space();
10864            self.write_keyword("LOCAL");
10865        }
10866        self.write_space();
10867        self.write_keyword("INPATH");
10868        self.write_space();
10869        self.write("'");
10870        self.write(&l.inpath);
10871        self.write("'");
10872
10873        if l.overwrite {
10874            self.write_space();
10875            self.write_keyword("OVERWRITE");
10876        }
10877
10878        self.write_space();
10879        self.write_keyword("INTO TABLE");
10880        self.write_space();
10881        self.generate_expression(&l.table)?;
10882
10883        // PARTITION clause
10884        if !l.partition.is_empty() {
10885            self.write_space();
10886            self.write_keyword("PARTITION");
10887            self.write("(");
10888            for (i, (col, val)) in l.partition.iter().enumerate() {
10889                if i > 0 {
10890                    self.write(", ");
10891                }
10892                self.generate_identifier(col)?;
10893                self.write(" = ");
10894                self.generate_expression(val)?;
10895            }
10896            self.write(")");
10897        }
10898
10899        // INPUTFORMAT clause
10900        if let Some(fmt) = &l.input_format {
10901            self.write_space();
10902            self.write_keyword("INPUTFORMAT");
10903            self.write_space();
10904            self.write("'");
10905            self.write(fmt);
10906            self.write("'");
10907        }
10908
10909        // SERDE clause
10910        if let Some(serde) = &l.serde {
10911            self.write_space();
10912            self.write_keyword("SERDE");
10913            self.write_space();
10914            self.write("'");
10915            self.write(serde);
10916            self.write("'");
10917        }
10918
10919        Ok(())
10920    }
10921
10922    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10923        self.write_keyword("PRAGMA");
10924        self.write_space();
10925
10926        // Schema prefix if present
10927        if let Some(schema) = &p.schema {
10928            self.generate_identifier(schema)?;
10929            self.write(".");
10930        }
10931
10932        // Pragma name
10933        self.generate_identifier(&p.name)?;
10934
10935        // Value assignment or function call
10936        if let Some(value) = &p.value {
10937            self.write(" = ");
10938            self.generate_expression(value)?;
10939        } else if !p.args.is_empty() {
10940            self.write("(");
10941            for (i, arg) in p.args.iter().enumerate() {
10942                if i > 0 {
10943                    self.write(", ");
10944                }
10945                self.generate_expression(arg)?;
10946            }
10947            self.write(")");
10948        }
10949
10950        Ok(())
10951    }
10952
10953    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10954        self.write_keyword("GRANT");
10955        self.write_space();
10956
10957        // Privileges (with optional column lists)
10958        for (i, privilege) in g.privileges.iter().enumerate() {
10959            if i > 0 {
10960                self.write(", ");
10961            }
10962            self.write_keyword(&privilege.name);
10963            // Output column list if present: SELECT(col1, col2)
10964            if !privilege.columns.is_empty() {
10965                self.write("(");
10966                for (j, col) in privilege.columns.iter().enumerate() {
10967                    if j > 0 {
10968                        self.write(", ");
10969                    }
10970                    self.write(col);
10971                }
10972                self.write(")");
10973            }
10974        }
10975
10976        self.write_space();
10977        self.write_keyword("ON");
10978        self.write_space();
10979
10980        // Object kind (TABLE, SCHEMA, etc.)
10981        if let Some(kind) = &g.kind {
10982            self.write_keyword(kind);
10983            self.write_space();
10984        }
10985
10986        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10987        {
10988            use crate::dialects::DialectType;
10989            let should_upper = matches!(
10990                self.config.dialect,
10991                Some(DialectType::PostgreSQL)
10992                    | Some(DialectType::CockroachDB)
10993                    | Some(DialectType::Materialize)
10994                    | Some(DialectType::RisingWave)
10995            ) && (g.kind.as_deref() == Some("FUNCTION")
10996                || g.kind.as_deref() == Some("PROCEDURE"));
10997            if should_upper {
10998                use crate::expressions::Identifier;
10999                let upper_id = Identifier {
11000                    name: g.securable.name.to_uppercase(),
11001                    quoted: g.securable.quoted,
11002                    ..g.securable.clone()
11003                };
11004                self.generate_identifier(&upper_id)?;
11005            } else {
11006                self.generate_identifier(&g.securable)?;
11007            }
11008        }
11009
11010        // Function parameter types (if present)
11011        if !g.function_params.is_empty() {
11012            self.write("(");
11013            for (i, param) in g.function_params.iter().enumerate() {
11014                if i > 0 {
11015                    self.write(", ");
11016                }
11017                self.write(param);
11018            }
11019            self.write(")");
11020        }
11021
11022        self.write_space();
11023        self.write_keyword("TO");
11024        self.write_space();
11025
11026        // Principals
11027        for (i, principal) in g.principals.iter().enumerate() {
11028            if i > 0 {
11029                self.write(", ");
11030            }
11031            if principal.is_role {
11032                self.write_keyword("ROLE");
11033                self.write_space();
11034            } else if principal.is_group {
11035                self.write_keyword("GROUP");
11036                self.write_space();
11037            }
11038            self.generate_identifier(&principal.name)?;
11039        }
11040
11041        // WITH GRANT OPTION
11042        if g.grant_option {
11043            self.write_space();
11044            self.write_keyword("WITH GRANT OPTION");
11045        }
11046
11047        // TSQL: AS principal
11048        if let Some(ref principal) = g.as_principal {
11049            self.write_space();
11050            self.write_keyword("AS");
11051            self.write_space();
11052            self.generate_identifier(principal)?;
11053        }
11054
11055        Ok(())
11056    }
11057
11058    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11059        self.write_keyword("REVOKE");
11060        self.write_space();
11061
11062        // GRANT OPTION FOR
11063        if r.grant_option {
11064            self.write_keyword("GRANT OPTION FOR");
11065            self.write_space();
11066        }
11067
11068        // Privileges (with optional column lists)
11069        for (i, privilege) in r.privileges.iter().enumerate() {
11070            if i > 0 {
11071                self.write(", ");
11072            }
11073            self.write_keyword(&privilege.name);
11074            // Output column list if present: SELECT(col1, col2)
11075            if !privilege.columns.is_empty() {
11076                self.write("(");
11077                for (j, col) in privilege.columns.iter().enumerate() {
11078                    if j > 0 {
11079                        self.write(", ");
11080                    }
11081                    self.write(col);
11082                }
11083                self.write(")");
11084            }
11085        }
11086
11087        self.write_space();
11088        self.write_keyword("ON");
11089        self.write_space();
11090
11091        // Object kind
11092        if let Some(kind) = &r.kind {
11093            self.write_keyword(kind);
11094            self.write_space();
11095        }
11096
11097        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11098        {
11099            use crate::dialects::DialectType;
11100            let should_upper = matches!(
11101                self.config.dialect,
11102                Some(DialectType::PostgreSQL)
11103                    | Some(DialectType::CockroachDB)
11104                    | Some(DialectType::Materialize)
11105                    | Some(DialectType::RisingWave)
11106            ) && (r.kind.as_deref() == Some("FUNCTION")
11107                || r.kind.as_deref() == Some("PROCEDURE"));
11108            if should_upper {
11109                use crate::expressions::Identifier;
11110                let upper_id = Identifier {
11111                    name: r.securable.name.to_uppercase(),
11112                    quoted: r.securable.quoted,
11113                    ..r.securable.clone()
11114                };
11115                self.generate_identifier(&upper_id)?;
11116            } else {
11117                self.generate_identifier(&r.securable)?;
11118            }
11119        }
11120
11121        // Function parameter types (if present)
11122        if !r.function_params.is_empty() {
11123            self.write("(");
11124            for (i, param) in r.function_params.iter().enumerate() {
11125                if i > 0 {
11126                    self.write(", ");
11127                }
11128                self.write(param);
11129            }
11130            self.write(")");
11131        }
11132
11133        self.write_space();
11134        self.write_keyword("FROM");
11135        self.write_space();
11136
11137        // Principals
11138        for (i, principal) in r.principals.iter().enumerate() {
11139            if i > 0 {
11140                self.write(", ");
11141            }
11142            if principal.is_role {
11143                self.write_keyword("ROLE");
11144                self.write_space();
11145            } else if principal.is_group {
11146                self.write_keyword("GROUP");
11147                self.write_space();
11148            }
11149            self.generate_identifier(&principal.name)?;
11150        }
11151
11152        // CASCADE or RESTRICT
11153        if r.cascade {
11154            self.write_space();
11155            self.write_keyword("CASCADE");
11156        } else if r.restrict {
11157            self.write_space();
11158            self.write_keyword("RESTRICT");
11159        }
11160
11161        Ok(())
11162    }
11163
11164    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11165        self.write_keyword("COMMENT");
11166
11167        // IF EXISTS
11168        if c.exists {
11169            self.write_space();
11170            self.write_keyword("IF EXISTS");
11171        }
11172
11173        self.write_space();
11174        self.write_keyword("ON");
11175
11176        // MATERIALIZED
11177        if c.materialized {
11178            self.write_space();
11179            self.write_keyword("MATERIALIZED");
11180        }
11181
11182        self.write_space();
11183        self.write_keyword(&c.kind);
11184        self.write_space();
11185
11186        // Object name
11187        self.generate_expression(&c.this)?;
11188
11189        self.write_space();
11190        self.write_keyword("IS");
11191        self.write_space();
11192
11193        // Comment expression
11194        self.generate_expression(&c.expression)?;
11195
11196        Ok(())
11197    }
11198
11199    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11200        self.write_keyword("SET");
11201
11202        for (i, item) in s.items.iter().enumerate() {
11203            if i > 0 {
11204                self.write(",");
11205            }
11206            self.write_space();
11207
11208            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11209            if let Some(ref kind) = item.kind {
11210                self.write_keyword(kind);
11211                self.write_space();
11212            }
11213
11214            // Check for special SET forms by name
11215            let name_str = match &item.name {
11216                Expression::Identifier(id) => Some(id.name.as_str()),
11217                _ => None,
11218            };
11219
11220            let is_transaction = name_str == Some("TRANSACTION");
11221            let is_character_set = name_str == Some("CHARACTER SET");
11222            let is_names = name_str == Some("NAMES");
11223            let is_collate = name_str == Some("COLLATE");
11224            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11225            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11226            let is_variable = has_variable_kind || name_has_variable_prefix;
11227            let is_value_only =
11228                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11229
11230            if is_transaction {
11231                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11232                self.write_keyword("TRANSACTION");
11233                if let Expression::Identifier(id) = &item.value {
11234                    if !id.name.is_empty() {
11235                        self.write_space();
11236                        self.write(&id.name);
11237                    }
11238                }
11239            } else if is_character_set {
11240                // Output: SET CHARACTER SET <charset>
11241                self.write_keyword("CHARACTER SET");
11242                self.write_space();
11243                self.generate_set_value(&item.value)?;
11244            } else if is_names {
11245                // Output: SET NAMES <charset>
11246                self.write_keyword("NAMES");
11247                self.write_space();
11248                self.generate_set_value(&item.value)?;
11249            } else if is_collate {
11250                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11251                self.write_keyword("COLLATE");
11252                self.write_space();
11253                self.generate_set_value(&item.value)?;
11254            } else if is_variable {
11255                // Output: SET [VARIABLE] <name> = <value>
11256                // If kind=VARIABLE, the keyword was already written above.
11257                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11258                if name_has_variable_prefix && !has_variable_kind {
11259                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11260                        self.write_keyword("VARIABLE");
11261                        self.write_space();
11262                    }
11263                }
11264                // Extract actual variable name (strip VARIABLE prefix if present)
11265                if let Some(ns) = name_str {
11266                    let var_name = if name_has_variable_prefix {
11267                        &ns["VARIABLE ".len()..]
11268                    } else {
11269                        ns
11270                    };
11271                    self.write(var_name);
11272                } else {
11273                    self.generate_expression(&item.name)?;
11274                }
11275                self.write(" = ");
11276                self.generate_set_value(&item.value)?;
11277            } else if is_value_only {
11278                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11279                self.generate_expression(&item.name)?;
11280            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11281                // SET key value without = (TSQL style)
11282                self.generate_expression(&item.name)?;
11283                self.write_space();
11284                self.generate_set_value(&item.value)?;
11285            } else {
11286                // Standard: variable = value
11287                // SET item names should not be quoted (they are config parameter names, not column refs)
11288                match &item.name {
11289                    Expression::Identifier(id) => {
11290                        self.write(&id.name);
11291                    }
11292                    _ => {
11293                        self.generate_expression(&item.name)?;
11294                    }
11295                }
11296                self.write(" = ");
11297                self.generate_set_value(&item.value)?;
11298            }
11299        }
11300
11301        Ok(())
11302    }
11303
11304    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11305    /// directly to avoid reserved keyword quoting.
11306    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11307        if let Expression::Identifier(id) = value {
11308            match id.name.as_str() {
11309                "DEFAULT" | "ON" | "OFF" => {
11310                    self.write_keyword(&id.name);
11311                    return Ok(());
11312                }
11313                _ => {}
11314            }
11315        }
11316        self.generate_expression(value)
11317    }
11318
11319    // ==================== Phase 4: Additional DDL Generation ====================
11320
11321    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11322        self.write_keyword("ALTER");
11323        // MySQL modifiers before VIEW
11324        if let Some(ref algorithm) = av.algorithm {
11325            self.write_space();
11326            self.write_keyword("ALGORITHM");
11327            self.write(" = ");
11328            self.write_keyword(algorithm);
11329        }
11330        if let Some(ref definer) = av.definer {
11331            self.write_space();
11332            self.write_keyword("DEFINER");
11333            self.write(" = ");
11334            self.write(definer);
11335        }
11336        if let Some(ref sql_security) = av.sql_security {
11337            self.write_space();
11338            self.write_keyword("SQL SECURITY");
11339            self.write(" = ");
11340            self.write_keyword(sql_security);
11341        }
11342        self.write_space();
11343        self.write_keyword("VIEW");
11344        self.write_space();
11345        self.generate_table(&av.name)?;
11346
11347        // Hive: Column aliases with optional COMMENT
11348        if !av.columns.is_empty() {
11349            self.write(" (");
11350            for (i, col) in av.columns.iter().enumerate() {
11351                if i > 0 {
11352                    self.write(", ");
11353                }
11354                self.generate_identifier(&col.name)?;
11355                if let Some(ref comment) = col.comment {
11356                    self.write_space();
11357                    self.write_keyword("COMMENT");
11358                    self.write(" ");
11359                    self.generate_string_literal(comment)?;
11360                }
11361            }
11362            self.write(")");
11363        }
11364
11365        // TSQL: WITH option before actions
11366        if let Some(ref opt) = av.with_option {
11367            self.write_space();
11368            self.write_keyword("WITH");
11369            self.write_space();
11370            self.write_keyword(opt);
11371        }
11372
11373        for action in &av.actions {
11374            self.write_space();
11375            match action {
11376                AlterViewAction::Rename(new_name) => {
11377                    self.write_keyword("RENAME TO");
11378                    self.write_space();
11379                    self.generate_table(new_name)?;
11380                }
11381                AlterViewAction::OwnerTo(owner) => {
11382                    self.write_keyword("OWNER TO");
11383                    self.write_space();
11384                    self.generate_identifier(owner)?;
11385                }
11386                AlterViewAction::SetSchema(schema) => {
11387                    self.write_keyword("SET SCHEMA");
11388                    self.write_space();
11389                    self.generate_identifier(schema)?;
11390                }
11391                AlterViewAction::SetAuthorization(auth) => {
11392                    self.write_keyword("SET AUTHORIZATION");
11393                    self.write_space();
11394                    self.write(auth);
11395                }
11396                AlterViewAction::AlterColumn { name, action } => {
11397                    self.write_keyword("ALTER COLUMN");
11398                    self.write_space();
11399                    self.generate_identifier(name)?;
11400                    self.write_space();
11401                    self.generate_alter_column_action(action)?;
11402                }
11403                AlterViewAction::AsSelect(query) => {
11404                    self.write_keyword("AS");
11405                    self.write_space();
11406                    self.generate_expression(query)?;
11407                }
11408                AlterViewAction::SetTblproperties(props) => {
11409                    self.write_keyword("SET TBLPROPERTIES");
11410                    self.write(" (");
11411                    for (i, (key, value)) in props.iter().enumerate() {
11412                        if i > 0 {
11413                            self.write(", ");
11414                        }
11415                        self.generate_string_literal(key)?;
11416                        self.write("=");
11417                        self.generate_string_literal(value)?;
11418                    }
11419                    self.write(")");
11420                }
11421                AlterViewAction::UnsetTblproperties(keys) => {
11422                    self.write_keyword("UNSET TBLPROPERTIES");
11423                    self.write(" (");
11424                    for (i, key) in keys.iter().enumerate() {
11425                        if i > 0 {
11426                            self.write(", ");
11427                        }
11428                        self.generate_string_literal(key)?;
11429                    }
11430                    self.write(")");
11431                }
11432            }
11433        }
11434
11435        Ok(())
11436    }
11437
11438    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11439        self.write_keyword("ALTER INDEX");
11440        self.write_space();
11441        self.generate_identifier(&ai.name)?;
11442
11443        if let Some(table) = &ai.table {
11444            self.write_space();
11445            self.write_keyword("ON");
11446            self.write_space();
11447            self.generate_table(table)?;
11448        }
11449
11450        for action in &ai.actions {
11451            self.write_space();
11452            match action {
11453                AlterIndexAction::Rename(new_name) => {
11454                    self.write_keyword("RENAME TO");
11455                    self.write_space();
11456                    self.generate_identifier(new_name)?;
11457                }
11458                AlterIndexAction::SetTablespace(tablespace) => {
11459                    self.write_keyword("SET TABLESPACE");
11460                    self.write_space();
11461                    self.generate_identifier(tablespace)?;
11462                }
11463                AlterIndexAction::Visible(visible) => {
11464                    if *visible {
11465                        self.write_keyword("VISIBLE");
11466                    } else {
11467                        self.write_keyword("INVISIBLE");
11468                    }
11469                }
11470            }
11471        }
11472
11473        Ok(())
11474    }
11475
11476    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11477        // Output leading comments
11478        for comment in &cs.leading_comments {
11479            self.write_formatted_comment(comment);
11480            self.write_space();
11481        }
11482
11483        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11484        let saved_athena_hive_context = self.athena_hive_context;
11485        if matches!(
11486            self.config.dialect,
11487            Some(crate::dialects::DialectType::Athena)
11488        ) {
11489            self.athena_hive_context = true;
11490        }
11491
11492        self.write_keyword("CREATE SCHEMA");
11493
11494        if cs.if_not_exists {
11495            self.write_space();
11496            self.write_keyword("IF NOT EXISTS");
11497        }
11498
11499        self.write_space();
11500        self.generate_identifier(&cs.name)?;
11501
11502        if let Some(ref clone_src) = cs.clone_from {
11503            self.write_keyword(" CLONE ");
11504            self.generate_identifier(clone_src)?;
11505        }
11506
11507        if let Some(ref at_clause) = cs.at_clause {
11508            self.write_space();
11509            self.generate_expression(at_clause)?;
11510        }
11511
11512        if let Some(auth) = &cs.authorization {
11513            self.write_space();
11514            self.write_keyword("AUTHORIZATION");
11515            self.write_space();
11516            self.generate_identifier(auth)?;
11517        }
11518
11519        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11520        // Separate WITH properties from other properties
11521        let with_properties: Vec<_> = cs
11522            .properties
11523            .iter()
11524            .filter(|p| matches!(p, Expression::Property(_)))
11525            .collect();
11526        let other_properties: Vec<_> = cs
11527            .properties
11528            .iter()
11529            .filter(|p| !matches!(p, Expression::Property(_)))
11530            .collect();
11531
11532        // Generate WITH (props) if we have Property expressions
11533        if !with_properties.is_empty() {
11534            self.write_space();
11535            self.write_keyword("WITH");
11536            self.write(" (");
11537            for (i, prop) in with_properties.iter().enumerate() {
11538                if i > 0 {
11539                    self.write(", ");
11540                }
11541                self.generate_expression(prop)?;
11542            }
11543            self.write(")");
11544        }
11545
11546        // Generate other properties (like DEFAULT COLLATE)
11547        for prop in other_properties {
11548            self.write_space();
11549            self.generate_expression(prop)?;
11550        }
11551
11552        // Restore Athena Hive context
11553        self.athena_hive_context = saved_athena_hive_context;
11554
11555        Ok(())
11556    }
11557
11558    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11559        self.write_keyword("DROP SCHEMA");
11560
11561        if ds.if_exists {
11562            self.write_space();
11563            self.write_keyword("IF EXISTS");
11564        }
11565
11566        self.write_space();
11567        self.generate_identifier(&ds.name)?;
11568
11569        if ds.cascade {
11570            self.write_space();
11571            self.write_keyword("CASCADE");
11572        }
11573
11574        Ok(())
11575    }
11576
11577    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11578        self.write_keyword("DROP NAMESPACE");
11579
11580        if dn.if_exists {
11581            self.write_space();
11582            self.write_keyword("IF EXISTS");
11583        }
11584
11585        self.write_space();
11586        self.generate_identifier(&dn.name)?;
11587
11588        if dn.cascade {
11589            self.write_space();
11590            self.write_keyword("CASCADE");
11591        }
11592
11593        Ok(())
11594    }
11595
11596    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11597        self.write_keyword("CREATE DATABASE");
11598
11599        if cd.if_not_exists {
11600            self.write_space();
11601            self.write_keyword("IF NOT EXISTS");
11602        }
11603
11604        self.write_space();
11605        self.generate_identifier(&cd.name)?;
11606
11607        if let Some(ref clone_src) = cd.clone_from {
11608            self.write_keyword(" CLONE ");
11609            self.generate_identifier(clone_src)?;
11610        }
11611
11612        // AT/BEFORE clause for time travel (Snowflake)
11613        if let Some(ref at_clause) = cd.at_clause {
11614            self.write_space();
11615            self.generate_expression(at_clause)?;
11616        }
11617
11618        for option in &cd.options {
11619            self.write_space();
11620            match option {
11621                DatabaseOption::CharacterSet(charset) => {
11622                    self.write_keyword("CHARACTER SET");
11623                    self.write(" = ");
11624                    self.write(&format!("'{}'", charset));
11625                }
11626                DatabaseOption::Collate(collate) => {
11627                    self.write_keyword("COLLATE");
11628                    self.write(" = ");
11629                    self.write(&format!("'{}'", collate));
11630                }
11631                DatabaseOption::Owner(owner) => {
11632                    self.write_keyword("OWNER");
11633                    self.write(" = ");
11634                    self.generate_identifier(owner)?;
11635                }
11636                DatabaseOption::Template(template) => {
11637                    self.write_keyword("TEMPLATE");
11638                    self.write(" = ");
11639                    self.generate_identifier(template)?;
11640                }
11641                DatabaseOption::Encoding(encoding) => {
11642                    self.write_keyword("ENCODING");
11643                    self.write(" = ");
11644                    self.write(&format!("'{}'", encoding));
11645                }
11646                DatabaseOption::Location(location) => {
11647                    self.write_keyword("LOCATION");
11648                    self.write(" = ");
11649                    self.write(&format!("'{}'", location));
11650                }
11651            }
11652        }
11653
11654        Ok(())
11655    }
11656
11657    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11658        self.write_keyword("DROP DATABASE");
11659
11660        if dd.if_exists {
11661            self.write_space();
11662            self.write_keyword("IF EXISTS");
11663        }
11664
11665        self.write_space();
11666        self.generate_identifier(&dd.name)?;
11667
11668        Ok(())
11669    }
11670
11671    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11672        self.write_keyword("CREATE");
11673
11674        if cf.or_replace {
11675            self.write_space();
11676            self.write_keyword("OR REPLACE");
11677        }
11678
11679        if cf.temporary {
11680            self.write_space();
11681            self.write_keyword("TEMPORARY");
11682        }
11683
11684        self.write_space();
11685        if cf.is_table_function {
11686            self.write_keyword("TABLE FUNCTION");
11687        } else {
11688            self.write_keyword("FUNCTION");
11689        }
11690
11691        if cf.if_not_exists {
11692            self.write_space();
11693            self.write_keyword("IF NOT EXISTS");
11694        }
11695
11696        self.write_space();
11697        self.generate_table(&cf.name)?;
11698        if cf.has_parens {
11699            let func_multiline = self.config.pretty
11700                && matches!(
11701                    self.config.dialect,
11702                    Some(crate::dialects::DialectType::TSQL)
11703                        | Some(crate::dialects::DialectType::Fabric)
11704                )
11705                && !cf.parameters.is_empty();
11706            if func_multiline {
11707                self.write("(\n");
11708                self.indent_level += 2;
11709                self.write_indent();
11710                self.generate_function_parameters(&cf.parameters)?;
11711                self.write("\n");
11712                self.indent_level -= 2;
11713                self.write(")");
11714            } else {
11715                self.write("(");
11716                self.generate_function_parameters(&cf.parameters)?;
11717                self.write(")");
11718            }
11719        }
11720
11721        // Output RETURNS clause (always comes first after parameters)
11722        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11723        let use_multiline = self.config.pretty
11724            && matches!(
11725                self.config.dialect,
11726                Some(crate::dialects::DialectType::BigQuery)
11727                    | Some(crate::dialects::DialectType::TSQL)
11728                    | Some(crate::dialects::DialectType::Fabric)
11729            );
11730
11731        if cf.language_first {
11732            // LANGUAGE first, then SQL data access, then RETURNS
11733            if let Some(lang) = &cf.language {
11734                if use_multiline {
11735                    self.write_newline();
11736                } else {
11737                    self.write_space();
11738                }
11739                self.write_keyword("LANGUAGE");
11740                self.write_space();
11741                self.write(lang);
11742            }
11743
11744            // SQL data access comes after LANGUAGE in this case
11745            if let Some(sql_data) = &cf.sql_data_access {
11746                self.write_space();
11747                match sql_data {
11748                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11749                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11750                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11751                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11752                }
11753            }
11754
11755            if let Some(ref rtb) = cf.returns_table_body {
11756                if use_multiline {
11757                    self.write_newline();
11758                } else {
11759                    self.write_space();
11760                }
11761                self.write_keyword("RETURNS");
11762                self.write_space();
11763                self.write(rtb);
11764            } else if let Some(return_type) = &cf.return_type {
11765                if use_multiline {
11766                    self.write_newline();
11767                } else {
11768                    self.write_space();
11769                }
11770                self.write_keyword("RETURNS");
11771                self.write_space();
11772                self.generate_data_type(return_type)?;
11773            }
11774        } else {
11775            // RETURNS first (default)
11776            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11777            let is_duckdb = matches!(
11778                self.config.dialect,
11779                Some(crate::dialects::DialectType::DuckDB)
11780            );
11781            if let Some(ref rtb) = cf.returns_table_body {
11782                if !(is_duckdb && rtb.is_empty()) {
11783                    if use_multiline {
11784                        self.write_newline();
11785                    } else {
11786                        self.write_space();
11787                    }
11788                    self.write_keyword("RETURNS");
11789                    self.write_space();
11790                    self.write(rtb);
11791                }
11792            } else if let Some(return_type) = &cf.return_type {
11793                if use_multiline {
11794                    self.write_newline();
11795                } else {
11796                    self.write_space();
11797                }
11798                self.write_keyword("RETURNS");
11799                self.write_space();
11800                self.generate_data_type(return_type)?;
11801            }
11802        }
11803
11804        // If we have property_order, use it to output properties in original order
11805        if !cf.property_order.is_empty() {
11806            // For BigQuery, OPTIONS must come before AS - reorder if needed
11807            let is_bigquery = matches!(
11808                self.config.dialect,
11809                Some(crate::dialects::DialectType::BigQuery)
11810            );
11811            let property_order = if is_bigquery {
11812                // Move Options before As if both are present
11813                let mut reordered = Vec::new();
11814                let mut has_as = false;
11815                let mut has_options = false;
11816                for prop in &cf.property_order {
11817                    match prop {
11818                        FunctionPropertyKind::As => has_as = true,
11819                        FunctionPropertyKind::Options => has_options = true,
11820                        _ => {}
11821                    }
11822                }
11823                if has_as && has_options {
11824                    // Output all props except As and Options, then Options, then As
11825                    for prop in &cf.property_order {
11826                        if *prop != FunctionPropertyKind::As
11827                            && *prop != FunctionPropertyKind::Options
11828                        {
11829                            reordered.push(*prop);
11830                        }
11831                    }
11832                    reordered.push(FunctionPropertyKind::Options);
11833                    reordered.push(FunctionPropertyKind::As);
11834                    reordered
11835                } else {
11836                    cf.property_order.clone()
11837                }
11838            } else {
11839                cf.property_order.clone()
11840            };
11841
11842            for prop in &property_order {
11843                match prop {
11844                    FunctionPropertyKind::Set => {
11845                        self.generate_function_set_options(cf)?;
11846                    }
11847                    FunctionPropertyKind::As => {
11848                        self.generate_function_body(cf)?;
11849                    }
11850                    FunctionPropertyKind::Language => {
11851                        if !cf.language_first {
11852                            // Only output here if not already output above
11853                            if let Some(lang) = &cf.language {
11854                                // Only BigQuery uses multiline formatting
11855                                let use_multiline = self.config.pretty
11856                                    && matches!(
11857                                        self.config.dialect,
11858                                        Some(crate::dialects::DialectType::BigQuery)
11859                                    );
11860                                if use_multiline {
11861                                    self.write_newline();
11862                                } else {
11863                                    self.write_space();
11864                                }
11865                                self.write_keyword("LANGUAGE");
11866                                self.write_space();
11867                                self.write(lang);
11868                            }
11869                        }
11870                    }
11871                    FunctionPropertyKind::Determinism => {
11872                        self.generate_function_determinism(cf)?;
11873                    }
11874                    FunctionPropertyKind::NullInput => {
11875                        self.generate_function_null_input(cf)?;
11876                    }
11877                    FunctionPropertyKind::Security => {
11878                        self.generate_function_security(cf)?;
11879                    }
11880                    FunctionPropertyKind::SqlDataAccess => {
11881                        if !cf.language_first {
11882                            // Only output here if not already output above
11883                            self.generate_function_sql_data_access(cf)?;
11884                        }
11885                    }
11886                    FunctionPropertyKind::Options => {
11887                        if !cf.options.is_empty() {
11888                            self.write_space();
11889                            self.generate_options_clause(&cf.options)?;
11890                        }
11891                    }
11892                    FunctionPropertyKind::Environment => {
11893                        if !cf.environment.is_empty() {
11894                            self.write_space();
11895                            self.generate_environment_clause(&cf.environment)?;
11896                        }
11897                    }
11898                }
11899            }
11900
11901            // Output OPTIONS if not tracked in property_order (legacy)
11902            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11903            {
11904                self.write_space();
11905                self.generate_options_clause(&cf.options)?;
11906            }
11907
11908            // Output ENVIRONMENT if not tracked in property_order (legacy)
11909            if !cf.environment.is_empty()
11910                && !cf
11911                    .property_order
11912                    .contains(&FunctionPropertyKind::Environment)
11913            {
11914                self.write_space();
11915                self.generate_environment_clause(&cf.environment)?;
11916            }
11917        } else {
11918            // Legacy behavior when property_order is empty
11919            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11920            if matches!(
11921                self.config.dialect,
11922                Some(crate::dialects::DialectType::BigQuery)
11923            ) {
11924                self.generate_function_determinism(cf)?;
11925            }
11926
11927            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11928            let use_multiline = self.config.pretty
11929                && matches!(
11930                    self.config.dialect,
11931                    Some(crate::dialects::DialectType::BigQuery)
11932                );
11933
11934            if !cf.language_first {
11935                if let Some(lang) = &cf.language {
11936                    if use_multiline {
11937                        self.write_newline();
11938                    } else {
11939                        self.write_space();
11940                    }
11941                    self.write_keyword("LANGUAGE");
11942                    self.write_space();
11943                    self.write(lang);
11944                }
11945
11946                // SQL data access characteristic comes after LANGUAGE
11947                self.generate_function_sql_data_access(cf)?;
11948            }
11949
11950            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11951            if !matches!(
11952                self.config.dialect,
11953                Some(crate::dialects::DialectType::BigQuery)
11954            ) {
11955                self.generate_function_determinism(cf)?;
11956            }
11957
11958            self.generate_function_null_input(cf)?;
11959            self.generate_function_security(cf)?;
11960            self.generate_function_set_options(cf)?;
11961
11962            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11963            if !cf.options.is_empty() {
11964                self.write_space();
11965                self.generate_options_clause(&cf.options)?;
11966            }
11967
11968            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11969            if !cf.environment.is_empty() {
11970                self.write_space();
11971                self.generate_environment_clause(&cf.environment)?;
11972            }
11973
11974            self.generate_function_body(cf)?;
11975        }
11976
11977        Ok(())
11978    }
11979
11980    /// Generate SET options for CREATE FUNCTION
11981    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11982        for opt in &cf.set_options {
11983            self.write_space();
11984            self.write_keyword("SET");
11985            self.write_space();
11986            self.write(&opt.name);
11987            match &opt.value {
11988                FunctionSetValue::Value { value, use_to } => {
11989                    if *use_to {
11990                        self.write(" TO ");
11991                    } else {
11992                        self.write(" = ");
11993                    }
11994                    self.write(value);
11995                }
11996                FunctionSetValue::FromCurrent => {
11997                    self.write_space();
11998                    self.write_keyword("FROM CURRENT");
11999                }
12000            }
12001        }
12002        Ok(())
12003    }
12004
12005    /// Generate function body (AS clause)
12006    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12007        if let Some(body) = &cf.body {
12008            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12009            self.write_space();
12010            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12011            let use_multiline = self.config.pretty
12012                && matches!(
12013                    self.config.dialect,
12014                    Some(crate::dialects::DialectType::BigQuery)
12015                );
12016            match body {
12017                FunctionBody::Block(block) => {
12018                    self.write_keyword("AS");
12019                    if matches!(
12020                        self.config.dialect,
12021                        Some(crate::dialects::DialectType::TSQL)
12022                    ) {
12023                        self.write(" BEGIN ");
12024                        self.write(block);
12025                        self.write(" END");
12026                    } else if matches!(
12027                        self.config.dialect,
12028                        Some(crate::dialects::DialectType::PostgreSQL)
12029                    ) {
12030                        self.write(" $$");
12031                        self.write(block);
12032                        self.write("$$");
12033                    } else {
12034                        // Escape content for single-quoted output
12035                        let escaped = self.escape_block_for_single_quote(block);
12036                        // In BigQuery pretty mode, body content goes on new line
12037                        if use_multiline {
12038                            self.write_newline();
12039                        } else {
12040                            self.write(" ");
12041                        }
12042                        self.write("'");
12043                        self.write(&escaped);
12044                        self.write("'");
12045                    }
12046                }
12047                FunctionBody::StringLiteral(s) => {
12048                    self.write_keyword("AS");
12049                    // In BigQuery pretty mode, body content goes on new line
12050                    if use_multiline {
12051                        self.write_newline();
12052                    } else {
12053                        self.write(" ");
12054                    }
12055                    self.write("'");
12056                    self.write(s);
12057                    self.write("'");
12058                }
12059                FunctionBody::Expression(expr) => {
12060                    self.write_keyword("AS");
12061                    self.write_space();
12062                    self.generate_expression(expr)?;
12063                }
12064                FunctionBody::External(name) => {
12065                    self.write_keyword("EXTERNAL NAME");
12066                    self.write(" '");
12067                    self.write(name);
12068                    self.write("'");
12069                }
12070                FunctionBody::Return(expr) => {
12071                    if matches!(
12072                        self.config.dialect,
12073                        Some(crate::dialects::DialectType::DuckDB)
12074                    ) {
12075                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12076                        self.write_keyword("AS");
12077                        self.write_space();
12078                        // Empty returns_table_body signals TABLE return
12079                        if cf.returns_table_body.is_some() {
12080                            self.write_keyword("TABLE");
12081                            self.write_space();
12082                        }
12083                        self.generate_expression(expr)?;
12084                    } else {
12085                        if self.config.create_function_return_as {
12086                            self.write_keyword("AS");
12087                            // TSQL pretty: newline between AS and RETURN
12088                            if self.config.pretty
12089                                && matches!(
12090                                    self.config.dialect,
12091                                    Some(crate::dialects::DialectType::TSQL)
12092                                        | Some(crate::dialects::DialectType::Fabric)
12093                                )
12094                            {
12095                                self.write_newline();
12096                            } else {
12097                                self.write_space();
12098                            }
12099                        }
12100                        self.write_keyword("RETURN");
12101                        self.write_space();
12102                        self.generate_expression(expr)?;
12103                    }
12104                }
12105                FunctionBody::Statements(stmts) => {
12106                    self.write_keyword("AS");
12107                    self.write(" BEGIN ");
12108                    for (i, stmt) in stmts.iter().enumerate() {
12109                        if i > 0 {
12110                            self.write(" ");
12111                        }
12112                        self.generate_expression(stmt)?;
12113                    }
12114                    self.write(" END");
12115                }
12116                FunctionBody::DollarQuoted { content, tag } => {
12117                    self.write_keyword("AS");
12118                    self.write(" ");
12119                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12120                    let supports_dollar_quoting = matches!(
12121                        self.config.dialect,
12122                        Some(crate::dialects::DialectType::PostgreSQL)
12123                            | Some(crate::dialects::DialectType::Databricks)
12124                            | Some(crate::dialects::DialectType::Redshift)
12125                            | Some(crate::dialects::DialectType::DuckDB)
12126                    );
12127                    if supports_dollar_quoting {
12128                        // Output in dollar-quoted format
12129                        self.write("$");
12130                        if let Some(t) = tag {
12131                            self.write(t);
12132                        }
12133                        self.write("$");
12134                        self.write(content);
12135                        self.write("$");
12136                        if let Some(t) = tag {
12137                            self.write(t);
12138                        }
12139                        self.write("$");
12140                    } else {
12141                        // Convert to single-quoted string for other dialects
12142                        let escaped = self.escape_block_for_single_quote(content);
12143                        self.write("'");
12144                        self.write(&escaped);
12145                        self.write("'");
12146                    }
12147                }
12148            }
12149        }
12150        Ok(())
12151    }
12152
12153    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12154    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12155        if let Some(det) = cf.deterministic {
12156            self.write_space();
12157            if matches!(
12158                self.config.dialect,
12159                Some(crate::dialects::DialectType::BigQuery)
12160            ) {
12161                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12162                if det {
12163                    self.write_keyword("DETERMINISTIC");
12164                } else {
12165                    self.write_keyword("NOT DETERMINISTIC");
12166                }
12167            } else {
12168                // PostgreSQL and others use IMMUTABLE/VOLATILE
12169                if det {
12170                    self.write_keyword("IMMUTABLE");
12171                } else {
12172                    self.write_keyword("VOLATILE");
12173                }
12174            }
12175        }
12176        Ok(())
12177    }
12178
12179    /// Generate null input handling clause
12180    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12181        if let Some(returns_null) = cf.returns_null_on_null_input {
12182            self.write_space();
12183            if returns_null {
12184                if cf.strict {
12185                    self.write_keyword("STRICT");
12186                } else {
12187                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12188                }
12189            } else {
12190                self.write_keyword("CALLED ON NULL INPUT");
12191            }
12192        }
12193        Ok(())
12194    }
12195
12196    /// Generate security clause
12197    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12198        if let Some(security) = &cf.security {
12199            self.write_space();
12200            self.write_keyword("SECURITY");
12201            self.write_space();
12202            match security {
12203                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12204                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12205                FunctionSecurity::None => self.write_keyword("NONE"),
12206            }
12207        }
12208        Ok(())
12209    }
12210
12211    /// Generate SQL data access clause
12212    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12213        if let Some(sql_data) = &cf.sql_data_access {
12214            self.write_space();
12215            match sql_data {
12216                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12217                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12218                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12219                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12220            }
12221        }
12222        Ok(())
12223    }
12224
12225    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12226        for (i, param) in params.iter().enumerate() {
12227            if i > 0 {
12228                self.write(", ");
12229            }
12230
12231            if let Some(mode) = &param.mode {
12232                if let Some(text) = &param.mode_text {
12233                    self.write(text);
12234                } else {
12235                    match mode {
12236                        ParameterMode::In => self.write_keyword("IN"),
12237                        ParameterMode::Out => self.write_keyword("OUT"),
12238                        ParameterMode::InOut => self.write_keyword("INOUT"),
12239                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12240                    }
12241                }
12242                self.write_space();
12243            }
12244
12245            if let Some(name) = &param.name {
12246                self.generate_identifier(name)?;
12247                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12248                let skip_type =
12249                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12250                if !skip_type {
12251                    self.write_space();
12252                    self.generate_data_type(&param.data_type)?;
12253                }
12254            } else {
12255                self.generate_data_type(&param.data_type)?;
12256            }
12257
12258            if let Some(default) = &param.default {
12259                if self.config.parameter_default_equals {
12260                    self.write(" = ");
12261                } else {
12262                    self.write(" DEFAULT ");
12263                }
12264                self.generate_expression(default)?;
12265            }
12266        }
12267
12268        Ok(())
12269    }
12270
12271    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12272        self.write_keyword("DROP FUNCTION");
12273
12274        if df.if_exists {
12275            self.write_space();
12276            self.write_keyword("IF EXISTS");
12277        }
12278
12279        self.write_space();
12280        self.generate_table(&df.name)?;
12281
12282        if let Some(params) = &df.parameters {
12283            self.write(" (");
12284            for (i, dt) in params.iter().enumerate() {
12285                if i > 0 {
12286                    self.write(", ");
12287                }
12288                self.generate_data_type(dt)?;
12289            }
12290            self.write(")");
12291        }
12292
12293        if df.cascade {
12294            self.write_space();
12295            self.write_keyword("CASCADE");
12296        }
12297
12298        Ok(())
12299    }
12300
12301    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12302        self.write_keyword("CREATE");
12303
12304        if cp.or_replace {
12305            self.write_space();
12306            self.write_keyword("OR REPLACE");
12307        }
12308
12309        self.write_space();
12310        if cp.use_proc_keyword {
12311            self.write_keyword("PROC");
12312        } else {
12313            self.write_keyword("PROCEDURE");
12314        }
12315
12316        if cp.if_not_exists {
12317            self.write_space();
12318            self.write_keyword("IF NOT EXISTS");
12319        }
12320
12321        self.write_space();
12322        self.generate_table(&cp.name)?;
12323        if cp.has_parens {
12324            self.write("(");
12325            self.generate_function_parameters(&cp.parameters)?;
12326            self.write(")");
12327        } else if !cp.parameters.is_empty() {
12328            // TSQL: unparenthesized parameters
12329            self.write_space();
12330            self.generate_function_parameters(&cp.parameters)?;
12331        }
12332
12333        // RETURNS clause (Snowflake)
12334        if let Some(return_type) = &cp.return_type {
12335            self.write_space();
12336            self.write_keyword("RETURNS");
12337            self.write_space();
12338            self.generate_data_type(return_type)?;
12339        }
12340
12341        // EXECUTE AS clause (Snowflake)
12342        if let Some(execute_as) = &cp.execute_as {
12343            self.write_space();
12344            self.write_keyword("EXECUTE AS");
12345            self.write_space();
12346            self.write_keyword(execute_as);
12347        }
12348
12349        if let Some(lang) = &cp.language {
12350            self.write_space();
12351            self.write_keyword("LANGUAGE");
12352            self.write_space();
12353            self.write(lang);
12354        }
12355
12356        if let Some(security) = &cp.security {
12357            self.write_space();
12358            self.write_keyword("SECURITY");
12359            self.write_space();
12360            match security {
12361                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12362                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12363                FunctionSecurity::None => self.write_keyword("NONE"),
12364            }
12365        }
12366
12367        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12368        if !cp.with_options.is_empty() {
12369            self.write_space();
12370            self.write_keyword("WITH");
12371            self.write_space();
12372            for (i, opt) in cp.with_options.iter().enumerate() {
12373                if i > 0 {
12374                    self.write(", ");
12375                }
12376                self.write(opt);
12377            }
12378        }
12379
12380        if let Some(body) = &cp.body {
12381            self.write_space();
12382            match body {
12383                FunctionBody::Block(block) => {
12384                    self.write_keyword("AS");
12385                    if matches!(
12386                        self.config.dialect,
12387                        Some(crate::dialects::DialectType::TSQL)
12388                    ) {
12389                        self.write(" BEGIN ");
12390                        self.write(block);
12391                        self.write(" END");
12392                    } else if matches!(
12393                        self.config.dialect,
12394                        Some(crate::dialects::DialectType::PostgreSQL)
12395                    ) {
12396                        self.write(" $$");
12397                        self.write(block);
12398                        self.write("$$");
12399                    } else {
12400                        // Escape content for single-quoted output
12401                        let escaped = self.escape_block_for_single_quote(block);
12402                        self.write(" '");
12403                        self.write(&escaped);
12404                        self.write("'");
12405                    }
12406                }
12407                FunctionBody::StringLiteral(s) => {
12408                    self.write_keyword("AS");
12409                    self.write(" '");
12410                    self.write(s);
12411                    self.write("'");
12412                }
12413                FunctionBody::Expression(expr) => {
12414                    self.write_keyword("AS");
12415                    self.write_space();
12416                    self.generate_expression(expr)?;
12417                }
12418                FunctionBody::External(name) => {
12419                    self.write_keyword("EXTERNAL NAME");
12420                    self.write(" '");
12421                    self.write(name);
12422                    self.write("'");
12423                }
12424                FunctionBody::Return(expr) => {
12425                    self.write_keyword("RETURN");
12426                    self.write_space();
12427                    self.generate_expression(expr)?;
12428                }
12429                FunctionBody::Statements(stmts) => {
12430                    self.write_keyword("AS");
12431                    self.write(" BEGIN ");
12432                    for (i, stmt) in stmts.iter().enumerate() {
12433                        if i > 0 {
12434                            self.write(" ");
12435                        }
12436                        self.generate_expression(stmt)?;
12437                    }
12438                    self.write(" END");
12439                }
12440                FunctionBody::DollarQuoted { content, tag } => {
12441                    self.write_keyword("AS");
12442                    self.write(" ");
12443                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12444                    let supports_dollar_quoting = matches!(
12445                        self.config.dialect,
12446                        Some(crate::dialects::DialectType::PostgreSQL)
12447                            | Some(crate::dialects::DialectType::Databricks)
12448                            | Some(crate::dialects::DialectType::Redshift)
12449                            | Some(crate::dialects::DialectType::DuckDB)
12450                    );
12451                    if supports_dollar_quoting {
12452                        // Output in dollar-quoted format
12453                        self.write("$");
12454                        if let Some(t) = tag {
12455                            self.write(t);
12456                        }
12457                        self.write("$");
12458                        self.write(content);
12459                        self.write("$");
12460                        if let Some(t) = tag {
12461                            self.write(t);
12462                        }
12463                        self.write("$");
12464                    } else {
12465                        // Convert to single-quoted string for other dialects
12466                        let escaped = self.escape_block_for_single_quote(content);
12467                        self.write("'");
12468                        self.write(&escaped);
12469                        self.write("'");
12470                    }
12471                }
12472            }
12473        }
12474
12475        Ok(())
12476    }
12477
12478    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12479        self.write_keyword("DROP PROCEDURE");
12480
12481        if dp.if_exists {
12482            self.write_space();
12483            self.write_keyword("IF EXISTS");
12484        }
12485
12486        self.write_space();
12487        self.generate_table(&dp.name)?;
12488
12489        if let Some(params) = &dp.parameters {
12490            self.write(" (");
12491            for (i, dt) in params.iter().enumerate() {
12492                if i > 0 {
12493                    self.write(", ");
12494                }
12495                self.generate_data_type(dt)?;
12496            }
12497            self.write(")");
12498        }
12499
12500        if dp.cascade {
12501            self.write_space();
12502            self.write_keyword("CASCADE");
12503        }
12504
12505        Ok(())
12506    }
12507
12508    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12509        self.write_keyword("CREATE");
12510
12511        if cs.or_replace {
12512            self.write_space();
12513            self.write_keyword("OR REPLACE");
12514        }
12515
12516        if cs.temporary {
12517            self.write_space();
12518            self.write_keyword("TEMPORARY");
12519        }
12520
12521        self.write_space();
12522        self.write_keyword("SEQUENCE");
12523
12524        if cs.if_not_exists {
12525            self.write_space();
12526            self.write_keyword("IF NOT EXISTS");
12527        }
12528
12529        self.write_space();
12530        self.generate_table(&cs.name)?;
12531
12532        // Output AS <type> if present
12533        if let Some(as_type) = &cs.as_type {
12534            self.write_space();
12535            self.write_keyword("AS");
12536            self.write_space();
12537            self.generate_data_type(as_type)?;
12538        }
12539
12540        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12541        if let Some(comment) = &cs.comment {
12542            self.write_space();
12543            self.write_keyword("COMMENT");
12544            self.write("=");
12545            self.generate_string_literal(comment)?;
12546        }
12547
12548        // If property_order is available, use it to preserve original order
12549        if !cs.property_order.is_empty() {
12550            for prop in &cs.property_order {
12551                match prop {
12552                    SeqPropKind::Start => {
12553                        if let Some(start) = cs.start {
12554                            self.write_space();
12555                            self.write_keyword("START WITH");
12556                            self.write(&format!(" {}", start));
12557                        }
12558                    }
12559                    SeqPropKind::Increment => {
12560                        if let Some(inc) = cs.increment {
12561                            self.write_space();
12562                            self.write_keyword("INCREMENT BY");
12563                            self.write(&format!(" {}", inc));
12564                        }
12565                    }
12566                    SeqPropKind::Minvalue => {
12567                        if let Some(min) = &cs.minvalue {
12568                            self.write_space();
12569                            match min {
12570                                SequenceBound::Value(v) => {
12571                                    self.write_keyword("MINVALUE");
12572                                    self.write(&format!(" {}", v));
12573                                }
12574                                SequenceBound::None => {
12575                                    self.write_keyword("NO MINVALUE");
12576                                }
12577                            }
12578                        }
12579                    }
12580                    SeqPropKind::Maxvalue => {
12581                        if let Some(max) = &cs.maxvalue {
12582                            self.write_space();
12583                            match max {
12584                                SequenceBound::Value(v) => {
12585                                    self.write_keyword("MAXVALUE");
12586                                    self.write(&format!(" {}", v));
12587                                }
12588                                SequenceBound::None => {
12589                                    self.write_keyword("NO MAXVALUE");
12590                                }
12591                            }
12592                        }
12593                    }
12594                    SeqPropKind::Cache => {
12595                        if let Some(cache) = cs.cache {
12596                            self.write_space();
12597                            self.write_keyword("CACHE");
12598                            self.write(&format!(" {}", cache));
12599                        }
12600                    }
12601                    SeqPropKind::NoCache => {
12602                        self.write_space();
12603                        self.write_keyword("NO CACHE");
12604                    }
12605                    SeqPropKind::NoCacheWord => {
12606                        self.write_space();
12607                        self.write_keyword("NOCACHE");
12608                    }
12609                    SeqPropKind::Cycle => {
12610                        self.write_space();
12611                        self.write_keyword("CYCLE");
12612                    }
12613                    SeqPropKind::NoCycle => {
12614                        self.write_space();
12615                        self.write_keyword("NO CYCLE");
12616                    }
12617                    SeqPropKind::NoCycleWord => {
12618                        self.write_space();
12619                        self.write_keyword("NOCYCLE");
12620                    }
12621                    SeqPropKind::OwnedBy => {
12622                        // Skip OWNED BY NONE (it's a no-op)
12623                        if !cs.owned_by_none {
12624                            if let Some(owned) = &cs.owned_by {
12625                                self.write_space();
12626                                self.write_keyword("OWNED BY");
12627                                self.write_space();
12628                                self.generate_table(owned)?;
12629                            }
12630                        }
12631                    }
12632                    SeqPropKind::Order => {
12633                        self.write_space();
12634                        self.write_keyword("ORDER");
12635                    }
12636                    SeqPropKind::NoOrder => {
12637                        self.write_space();
12638                        self.write_keyword("NOORDER");
12639                    }
12640                    SeqPropKind::Comment => {
12641                        // COMMENT is output above, before property_order iteration
12642                    }
12643                    SeqPropKind::Sharing => {
12644                        if let Some(val) = &cs.sharing {
12645                            self.write_space();
12646                            self.write(&format!("SHARING={}", val));
12647                        }
12648                    }
12649                    SeqPropKind::Keep => {
12650                        self.write_space();
12651                        self.write_keyword("KEEP");
12652                    }
12653                    SeqPropKind::NoKeep => {
12654                        self.write_space();
12655                        self.write_keyword("NOKEEP");
12656                    }
12657                    SeqPropKind::Scale => {
12658                        self.write_space();
12659                        self.write_keyword("SCALE");
12660                        if let Some(modifier) = &cs.scale_modifier {
12661                            if !modifier.is_empty() {
12662                                self.write_space();
12663                                self.write_keyword(modifier);
12664                            }
12665                        }
12666                    }
12667                    SeqPropKind::NoScale => {
12668                        self.write_space();
12669                        self.write_keyword("NOSCALE");
12670                    }
12671                    SeqPropKind::Shard => {
12672                        self.write_space();
12673                        self.write_keyword("SHARD");
12674                        if let Some(modifier) = &cs.shard_modifier {
12675                            if !modifier.is_empty() {
12676                                self.write_space();
12677                                self.write_keyword(modifier);
12678                            }
12679                        }
12680                    }
12681                    SeqPropKind::NoShard => {
12682                        self.write_space();
12683                        self.write_keyword("NOSHARD");
12684                    }
12685                    SeqPropKind::Session => {
12686                        self.write_space();
12687                        self.write_keyword("SESSION");
12688                    }
12689                    SeqPropKind::Global => {
12690                        self.write_space();
12691                        self.write_keyword("GLOBAL");
12692                    }
12693                    SeqPropKind::NoMinvalueWord => {
12694                        self.write_space();
12695                        self.write_keyword("NOMINVALUE");
12696                    }
12697                    SeqPropKind::NoMaxvalueWord => {
12698                        self.write_space();
12699                        self.write_keyword("NOMAXVALUE");
12700                    }
12701                }
12702            }
12703        } else {
12704            // Fallback: default order for backwards compatibility
12705            if let Some(inc) = cs.increment {
12706                self.write_space();
12707                self.write_keyword("INCREMENT BY");
12708                self.write(&format!(" {}", inc));
12709            }
12710
12711            if let Some(min) = &cs.minvalue {
12712                self.write_space();
12713                match min {
12714                    SequenceBound::Value(v) => {
12715                        self.write_keyword("MINVALUE");
12716                        self.write(&format!(" {}", v));
12717                    }
12718                    SequenceBound::None => {
12719                        self.write_keyword("NO MINVALUE");
12720                    }
12721                }
12722            }
12723
12724            if let Some(max) = &cs.maxvalue {
12725                self.write_space();
12726                match max {
12727                    SequenceBound::Value(v) => {
12728                        self.write_keyword("MAXVALUE");
12729                        self.write(&format!(" {}", v));
12730                    }
12731                    SequenceBound::None => {
12732                        self.write_keyword("NO MAXVALUE");
12733                    }
12734                }
12735            }
12736
12737            if let Some(start) = cs.start {
12738                self.write_space();
12739                self.write_keyword("START WITH");
12740                self.write(&format!(" {}", start));
12741            }
12742
12743            if let Some(cache) = cs.cache {
12744                self.write_space();
12745                self.write_keyword("CACHE");
12746                self.write(&format!(" {}", cache));
12747            }
12748
12749            if cs.cycle {
12750                self.write_space();
12751                self.write_keyword("CYCLE");
12752            }
12753
12754            if let Some(owned) = &cs.owned_by {
12755                self.write_space();
12756                self.write_keyword("OWNED BY");
12757                self.write_space();
12758                self.generate_table(owned)?;
12759            }
12760        }
12761
12762        Ok(())
12763    }
12764
12765    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12766        self.write_keyword("DROP SEQUENCE");
12767
12768        if ds.if_exists {
12769            self.write_space();
12770            self.write_keyword("IF EXISTS");
12771        }
12772
12773        self.write_space();
12774        self.generate_table(&ds.name)?;
12775
12776        if ds.cascade {
12777            self.write_space();
12778            self.write_keyword("CASCADE");
12779        }
12780
12781        Ok(())
12782    }
12783
12784    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12785        self.write_keyword("ALTER SEQUENCE");
12786
12787        if als.if_exists {
12788            self.write_space();
12789            self.write_keyword("IF EXISTS");
12790        }
12791
12792        self.write_space();
12793        self.generate_table(&als.name)?;
12794
12795        if let Some(inc) = als.increment {
12796            self.write_space();
12797            self.write_keyword("INCREMENT BY");
12798            self.write(&format!(" {}", inc));
12799        }
12800
12801        if let Some(min) = &als.minvalue {
12802            self.write_space();
12803            match min {
12804                SequenceBound::Value(v) => {
12805                    self.write_keyword("MINVALUE");
12806                    self.write(&format!(" {}", v));
12807                }
12808                SequenceBound::None => {
12809                    self.write_keyword("NO MINVALUE");
12810                }
12811            }
12812        }
12813
12814        if let Some(max) = &als.maxvalue {
12815            self.write_space();
12816            match max {
12817                SequenceBound::Value(v) => {
12818                    self.write_keyword("MAXVALUE");
12819                    self.write(&format!(" {}", v));
12820                }
12821                SequenceBound::None => {
12822                    self.write_keyword("NO MAXVALUE");
12823                }
12824            }
12825        }
12826
12827        if let Some(start) = als.start {
12828            self.write_space();
12829            self.write_keyword("START WITH");
12830            self.write(&format!(" {}", start));
12831        }
12832
12833        if let Some(restart) = &als.restart {
12834            self.write_space();
12835            self.write_keyword("RESTART");
12836            if let Some(val) = restart {
12837                self.write_keyword(" WITH");
12838                self.write(&format!(" {}", val));
12839            }
12840        }
12841
12842        if let Some(cache) = als.cache {
12843            self.write_space();
12844            self.write_keyword("CACHE");
12845            self.write(&format!(" {}", cache));
12846        }
12847
12848        if let Some(cycle) = als.cycle {
12849            self.write_space();
12850            if cycle {
12851                self.write_keyword("CYCLE");
12852            } else {
12853                self.write_keyword("NO CYCLE");
12854            }
12855        }
12856
12857        if let Some(owned) = &als.owned_by {
12858            self.write_space();
12859            self.write_keyword("OWNED BY");
12860            self.write_space();
12861            if let Some(table) = owned {
12862                self.generate_table(table)?;
12863            } else {
12864                self.write_keyword("NONE");
12865            }
12866        }
12867
12868        Ok(())
12869    }
12870
12871    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12872        self.write_keyword("CREATE");
12873
12874        if ct.or_replace {
12875            self.write_space();
12876            self.write_keyword("OR REPLACE");
12877        }
12878
12879        if ct.constraint {
12880            self.write_space();
12881            self.write_keyword("CONSTRAINT");
12882        }
12883
12884        self.write_space();
12885        self.write_keyword("TRIGGER");
12886        self.write_space();
12887        self.generate_identifier(&ct.name)?;
12888
12889        self.write_space();
12890        match ct.timing {
12891            TriggerTiming::Before => self.write_keyword("BEFORE"),
12892            TriggerTiming::After => self.write_keyword("AFTER"),
12893            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12894        }
12895
12896        // Events
12897        for (i, event) in ct.events.iter().enumerate() {
12898            if i > 0 {
12899                self.write_keyword(" OR");
12900            }
12901            self.write_space();
12902            match event {
12903                TriggerEvent::Insert => self.write_keyword("INSERT"),
12904                TriggerEvent::Update(cols) => {
12905                    self.write_keyword("UPDATE");
12906                    if let Some(cols) = cols {
12907                        self.write_space();
12908                        self.write_keyword("OF");
12909                        for (j, col) in cols.iter().enumerate() {
12910                            if j > 0 {
12911                                self.write(",");
12912                            }
12913                            self.write_space();
12914                            self.generate_identifier(col)?;
12915                        }
12916                    }
12917                }
12918                TriggerEvent::Delete => self.write_keyword("DELETE"),
12919                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12920            }
12921        }
12922
12923        self.write_space();
12924        self.write_keyword("ON");
12925        self.write_space();
12926        self.generate_table(&ct.table)?;
12927
12928        // Referencing clause
12929        if let Some(ref_clause) = &ct.referencing {
12930            self.write_space();
12931            self.write_keyword("REFERENCING");
12932            if let Some(old_table) = &ref_clause.old_table {
12933                self.write_space();
12934                self.write_keyword("OLD TABLE AS");
12935                self.write_space();
12936                self.generate_identifier(old_table)?;
12937            }
12938            if let Some(new_table) = &ref_clause.new_table {
12939                self.write_space();
12940                self.write_keyword("NEW TABLE AS");
12941                self.write_space();
12942                self.generate_identifier(new_table)?;
12943            }
12944            if let Some(old_row) = &ref_clause.old_row {
12945                self.write_space();
12946                self.write_keyword("OLD ROW AS");
12947                self.write_space();
12948                self.generate_identifier(old_row)?;
12949            }
12950            if let Some(new_row) = &ref_clause.new_row {
12951                self.write_space();
12952                self.write_keyword("NEW ROW AS");
12953                self.write_space();
12954                self.generate_identifier(new_row)?;
12955            }
12956        }
12957
12958        // Deferrable options for constraint triggers (must come before FOR EACH)
12959        if let Some(deferrable) = ct.deferrable {
12960            self.write_space();
12961            if deferrable {
12962                self.write_keyword("DEFERRABLE");
12963            } else {
12964                self.write_keyword("NOT DEFERRABLE");
12965            }
12966        }
12967
12968        if let Some(initially) = ct.initially_deferred {
12969            self.write_space();
12970            self.write_keyword("INITIALLY");
12971            self.write_space();
12972            if initially {
12973                self.write_keyword("DEFERRED");
12974            } else {
12975                self.write_keyword("IMMEDIATE");
12976            }
12977        }
12978
12979        self.write_space();
12980        self.write_keyword("FOR EACH");
12981        self.write_space();
12982        match ct.for_each {
12983            TriggerForEach::Row => self.write_keyword("ROW"),
12984            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12985        }
12986
12987        // When clause
12988        if let Some(when) = &ct.when {
12989            self.write_space();
12990            self.write_keyword("WHEN");
12991            self.write(" (");
12992            self.generate_expression(when)?;
12993            self.write(")");
12994        }
12995
12996        // Body
12997        self.write_space();
12998        match &ct.body {
12999            TriggerBody::Execute { function, args } => {
13000                self.write_keyword("EXECUTE FUNCTION");
13001                self.write_space();
13002                self.generate_table(function)?;
13003                self.write("(");
13004                for (i, arg) in args.iter().enumerate() {
13005                    if i > 0 {
13006                        self.write(", ");
13007                    }
13008                    self.generate_expression(arg)?;
13009                }
13010                self.write(")");
13011            }
13012            TriggerBody::Block(block) => {
13013                self.write_keyword("BEGIN");
13014                self.write_space();
13015                self.write(block);
13016                self.write_space();
13017                self.write_keyword("END");
13018            }
13019        }
13020
13021        Ok(())
13022    }
13023
13024    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13025        self.write_keyword("DROP TRIGGER");
13026
13027        if dt.if_exists {
13028            self.write_space();
13029            self.write_keyword("IF EXISTS");
13030        }
13031
13032        self.write_space();
13033        self.generate_identifier(&dt.name)?;
13034
13035        if let Some(table) = &dt.table {
13036            self.write_space();
13037            self.write_keyword("ON");
13038            self.write_space();
13039            self.generate_table(table)?;
13040        }
13041
13042        if dt.cascade {
13043            self.write_space();
13044            self.write_keyword("CASCADE");
13045        }
13046
13047        Ok(())
13048    }
13049
13050    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13051        self.write_keyword("CREATE TYPE");
13052
13053        if ct.if_not_exists {
13054            self.write_space();
13055            self.write_keyword("IF NOT EXISTS");
13056        }
13057
13058        self.write_space();
13059        self.generate_table(&ct.name)?;
13060
13061        self.write_space();
13062        self.write_keyword("AS");
13063        self.write_space();
13064
13065        match &ct.definition {
13066            TypeDefinition::Enum(values) => {
13067                self.write_keyword("ENUM");
13068                self.write(" (");
13069                for (i, val) in values.iter().enumerate() {
13070                    if i > 0 {
13071                        self.write(", ");
13072                    }
13073                    self.write(&format!("'{}'", val));
13074                }
13075                self.write(")");
13076            }
13077            TypeDefinition::Composite(attrs) => {
13078                self.write("(");
13079                for (i, attr) in attrs.iter().enumerate() {
13080                    if i > 0 {
13081                        self.write(", ");
13082                    }
13083                    self.generate_identifier(&attr.name)?;
13084                    self.write_space();
13085                    self.generate_data_type(&attr.data_type)?;
13086                    if let Some(collate) = &attr.collate {
13087                        self.write_space();
13088                        self.write_keyword("COLLATE");
13089                        self.write_space();
13090                        self.generate_identifier(collate)?;
13091                    }
13092                }
13093                self.write(")");
13094            }
13095            TypeDefinition::Range {
13096                subtype,
13097                subtype_diff,
13098                canonical,
13099            } => {
13100                self.write_keyword("RANGE");
13101                self.write(" (");
13102                self.write_keyword("SUBTYPE");
13103                self.write(" = ");
13104                self.generate_data_type(subtype)?;
13105                if let Some(diff) = subtype_diff {
13106                    self.write(", ");
13107                    self.write_keyword("SUBTYPE_DIFF");
13108                    self.write(" = ");
13109                    self.write(diff);
13110                }
13111                if let Some(canon) = canonical {
13112                    self.write(", ");
13113                    self.write_keyword("CANONICAL");
13114                    self.write(" = ");
13115                    self.write(canon);
13116                }
13117                self.write(")");
13118            }
13119            TypeDefinition::Base {
13120                input,
13121                output,
13122                internallength,
13123            } => {
13124                self.write("(");
13125                self.write_keyword("INPUT");
13126                self.write(" = ");
13127                self.write(input);
13128                self.write(", ");
13129                self.write_keyword("OUTPUT");
13130                self.write(" = ");
13131                self.write(output);
13132                if let Some(len) = internallength {
13133                    self.write(", ");
13134                    self.write_keyword("INTERNALLENGTH");
13135                    self.write(" = ");
13136                    self.write(&len.to_string());
13137                }
13138                self.write(")");
13139            }
13140            TypeDefinition::Domain {
13141                base_type,
13142                default,
13143                constraints,
13144            } => {
13145                self.generate_data_type(base_type)?;
13146                if let Some(def) = default {
13147                    self.write_space();
13148                    self.write_keyword("DEFAULT");
13149                    self.write_space();
13150                    self.generate_expression(def)?;
13151                }
13152                for constr in constraints {
13153                    self.write_space();
13154                    if let Some(name) = &constr.name {
13155                        self.write_keyword("CONSTRAINT");
13156                        self.write_space();
13157                        self.generate_identifier(name)?;
13158                        self.write_space();
13159                    }
13160                    self.write_keyword("CHECK");
13161                    self.write(" (");
13162                    self.generate_expression(&constr.check)?;
13163                    self.write(")");
13164                }
13165            }
13166        }
13167
13168        Ok(())
13169    }
13170
13171    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13172        self.write_keyword("DROP TYPE");
13173
13174        if dt.if_exists {
13175            self.write_space();
13176            self.write_keyword("IF EXISTS");
13177        }
13178
13179        self.write_space();
13180        self.generate_table(&dt.name)?;
13181
13182        if dt.cascade {
13183            self.write_space();
13184            self.write_keyword("CASCADE");
13185        }
13186
13187        Ok(())
13188    }
13189
13190    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13191        // Athena: DESCRIBE uses Hive engine (backticks)
13192        let saved_athena_hive_context = self.athena_hive_context;
13193        if matches!(
13194            self.config.dialect,
13195            Some(crate::dialects::DialectType::Athena)
13196        ) {
13197            self.athena_hive_context = true;
13198        }
13199
13200        // Output leading comments before DESCRIBE
13201        for comment in &d.leading_comments {
13202            self.write_formatted_comment(comment);
13203            self.write(" ");
13204        }
13205
13206        self.write_keyword("DESCRIBE");
13207
13208        if d.extended {
13209            self.write_space();
13210            self.write_keyword("EXTENDED");
13211        } else if d.formatted {
13212            self.write_space();
13213            self.write_keyword("FORMATTED");
13214        }
13215
13216        // Output style like ANALYZE, HISTORY
13217        if let Some(ref style) = d.style {
13218            self.write_space();
13219            self.write_keyword(style);
13220        }
13221
13222        // Handle object kind (TABLE, VIEW) based on dialect
13223        let should_output_kind = match self.config.dialect {
13224            // Spark doesn't use TABLE/VIEW after DESCRIBE
13225            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13226                false
13227            }
13228            // Snowflake always includes TABLE
13229            Some(DialectType::Snowflake) => true,
13230            _ => d.kind.is_some(),
13231        };
13232        if should_output_kind {
13233            if let Some(ref kind) = d.kind {
13234                self.write_space();
13235                self.write_keyword(kind);
13236            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13237                self.write_space();
13238                self.write_keyword("TABLE");
13239            }
13240        }
13241
13242        self.write_space();
13243        self.generate_expression(&d.target)?;
13244
13245        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13246        if let Some(ref partition) = d.partition {
13247            self.write_space();
13248            self.generate_expression(partition)?;
13249        }
13250
13251        // Databricks: AS JSON
13252        if d.as_json {
13253            self.write_space();
13254            self.write_keyword("AS JSON");
13255        }
13256
13257        // Output properties like type=stage
13258        for (name, value) in &d.properties {
13259            self.write_space();
13260            self.write(name);
13261            self.write("=");
13262            self.write(value);
13263        }
13264
13265        // Restore Athena Hive context
13266        self.athena_hive_context = saved_athena_hive_context;
13267
13268        Ok(())
13269    }
13270
13271    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13272    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13273    fn generate_show(&mut self, s: &Show) -> Result<()> {
13274        self.write_keyword("SHOW");
13275        self.write_space();
13276
13277        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13278        // where TERSE is syntactically valid but has no effect on output
13279        let show_terse = s.terse
13280            && !matches!(
13281                s.this.as_str(),
13282                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13283            );
13284        if show_terse {
13285            self.write_keyword("TERSE");
13286            self.write_space();
13287        }
13288
13289        // Object type (USERS, TABLES, DATABASES, etc.)
13290        self.write_keyword(&s.this);
13291
13292        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13293        if let Some(ref target_expr) = s.target {
13294            self.write_space();
13295            self.generate_expression(target_expr)?;
13296        }
13297
13298        // HISTORY keyword
13299        if s.history {
13300            self.write_space();
13301            self.write_keyword("HISTORY");
13302        }
13303
13304        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13305        if let Some(ref for_target) = s.for_target {
13306            self.write_space();
13307            self.write_keyword("FOR");
13308            self.write_space();
13309            self.generate_expression(for_target)?;
13310        }
13311
13312        // Determine ordering based on dialect:
13313        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13314        // - MySQL: IN, FROM, LIKE (when FROM is present)
13315        use crate::dialects::DialectType;
13316        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13317
13318        if !is_snowflake && s.from.is_some() {
13319            // MySQL ordering: IN, FROM, LIKE
13320
13321            // IN scope_kind [scope]
13322            if let Some(ref scope_kind) = s.scope_kind {
13323                self.write_space();
13324                self.write_keyword("IN");
13325                self.write_space();
13326                self.write_keyword(scope_kind);
13327                if let Some(ref scope) = s.scope {
13328                    self.write_space();
13329                    self.generate_expression(scope)?;
13330                }
13331            } else if let Some(ref scope) = s.scope {
13332                self.write_space();
13333                self.write_keyword("IN");
13334                self.write_space();
13335                self.generate_expression(scope)?;
13336            }
13337
13338            // FROM clause
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            // Second FROM clause (db name)
13347            if let Some(ref db) = s.db {
13348                self.write_space();
13349                self.write_keyword("FROM");
13350                self.write_space();
13351                self.generate_expression(db)?;
13352            }
13353
13354            // LIKE pattern
13355            if let Some(ref like) = s.like {
13356                self.write_space();
13357                self.write_keyword("LIKE");
13358                self.write_space();
13359                self.generate_expression(like)?;
13360            }
13361        } else {
13362            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13363
13364            // LIKE pattern
13365            if let Some(ref like) = s.like {
13366                self.write_space();
13367                self.write_keyword("LIKE");
13368                self.write_space();
13369                self.generate_expression(like)?;
13370            }
13371
13372            // IN scope_kind [scope]
13373            if let Some(ref scope_kind) = s.scope_kind {
13374                self.write_space();
13375                self.write_keyword("IN");
13376                self.write_space();
13377                self.write_keyword(scope_kind);
13378                if let Some(ref scope) = s.scope {
13379                    self.write_space();
13380                    self.generate_expression(scope)?;
13381                }
13382            } else if let Some(ref scope) = s.scope {
13383                self.write_space();
13384                self.write_keyword("IN");
13385                self.write_space();
13386                self.generate_expression(scope)?;
13387            }
13388        }
13389
13390        // STARTS WITH pattern
13391        if let Some(ref starts_with) = s.starts_with {
13392            self.write_space();
13393            self.write_keyword("STARTS WITH");
13394            self.write_space();
13395            self.generate_expression(starts_with)?;
13396        }
13397
13398        // LIMIT clause
13399        if let Some(ref limit) = s.limit {
13400            self.write_space();
13401            self.generate_limit(limit)?;
13402        }
13403
13404        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13405        if is_snowflake {
13406            if let Some(ref from) = s.from {
13407                self.write_space();
13408                self.write_keyword("FROM");
13409                self.write_space();
13410                self.generate_expression(from)?;
13411            }
13412        }
13413
13414        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13415        if let Some(ref where_clause) = s.where_clause {
13416            self.write_space();
13417            self.write_keyword("WHERE");
13418            self.write_space();
13419            self.generate_expression(where_clause)?;
13420        }
13421
13422        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13423        if let Some(is_mutex) = s.mutex {
13424            self.write_space();
13425            if is_mutex {
13426                self.write_keyword("MUTEX");
13427            } else {
13428                self.write_keyword("STATUS");
13429            }
13430        }
13431
13432        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13433        if !s.privileges.is_empty() {
13434            self.write_space();
13435            self.write_keyword("WITH PRIVILEGES");
13436            self.write_space();
13437            for (i, priv_name) in s.privileges.iter().enumerate() {
13438                if i > 0 {
13439                    self.write(", ");
13440                }
13441                self.write_keyword(priv_name);
13442            }
13443        }
13444
13445        Ok(())
13446    }
13447
13448    // ==================== End DDL Generation ====================
13449
13450    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13451        use crate::dialects::DialectType;
13452        match lit {
13453            Literal::String(s) => {
13454                self.generate_string_literal(s)?;
13455            }
13456            Literal::Number(n) => {
13457                if matches!(self.config.dialect, Some(DialectType::MySQL))
13458                    && n.len() > 2
13459                    && (n.starts_with("0x") || n.starts_with("0X"))
13460                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13461                {
13462                    return self.generate_identifier(&Identifier {
13463                        name: n.clone(),
13464                        quoted: true,
13465                        trailing_comments: Vec::new(),
13466                        span: None,
13467                    });
13468                }
13469                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13470                // for dialects that don't support them (MySQL interprets as identifier).
13471                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13472                let n = if n.contains('_')
13473                    && !matches!(
13474                        self.config.dialect,
13475                        Some(DialectType::ClickHouse)
13476                            | Some(DialectType::DuckDB)
13477                            | Some(DialectType::PostgreSQL)
13478                            | Some(DialectType::Hive)
13479                            | Some(DialectType::Spark)
13480                            | Some(DialectType::Databricks)
13481                    ) {
13482                    std::borrow::Cow::Owned(n.replace('_', ""))
13483                } else {
13484                    std::borrow::Cow::Borrowed(n.as_str())
13485                };
13486                // Normalize numbers starting with decimal point to have leading zero
13487                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13488                if n.starts_with('.') {
13489                    self.write("0");
13490                    self.write(&n);
13491                } else if n.starts_with("-.") {
13492                    // Handle negative numbers like -.25 -> -0.25
13493                    self.write("-0");
13494                    self.write(&n[1..]);
13495                } else {
13496                    self.write(&n);
13497                }
13498            }
13499            Literal::HexString(h) => {
13500                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13501                match self.config.dialect {
13502                    Some(DialectType::Spark)
13503                    | Some(DialectType::Databricks)
13504                    | Some(DialectType::Teradata) => self.write("X'"),
13505                    _ => self.write("x'"),
13506                }
13507                self.write(h);
13508                self.write("'");
13509            }
13510            Literal::HexNumber(h) => {
13511                // Hex number (0xA) - integer in hex notation (from BigQuery)
13512                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13513                // For other dialects, convert to decimal integer
13514                match self.config.dialect {
13515                    Some(DialectType::BigQuery)
13516                    | Some(DialectType::TSQL)
13517                    | Some(DialectType::Fabric) => {
13518                        self.write("0x");
13519                        self.write(h);
13520                    }
13521                    _ => {
13522                        // Convert hex to decimal
13523                        if let Ok(val) = u64::from_str_radix(h, 16) {
13524                            self.write(&val.to_string());
13525                        } else {
13526                            // Fallback: keep as 0x notation
13527                            self.write("0x");
13528                            self.write(h);
13529                        }
13530                    }
13531                }
13532            }
13533            Literal::BitString(b) => {
13534                // Bit string B'0101...'
13535                self.write("B'");
13536                self.write(b);
13537                self.write("'");
13538            }
13539            Literal::ByteString(b) => {
13540                // Byte string b'...' (BigQuery style)
13541                self.write("b'");
13542                // Escape special characters for output
13543                self.write_escaped_byte_string(b);
13544                self.write("'");
13545            }
13546            Literal::NationalString(s) => {
13547                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13548                // Other dialects strip the N prefix and output as regular string
13549                let keep_n_prefix = matches!(
13550                    self.config.dialect,
13551                    Some(DialectType::TSQL)
13552                        | Some(DialectType::Oracle)
13553                        | Some(DialectType::MySQL)
13554                        | None
13555                );
13556                if keep_n_prefix {
13557                    self.write("N'");
13558                } else {
13559                    self.write("'");
13560                }
13561                self.write(s);
13562                self.write("'");
13563            }
13564            Literal::Date(d) => {
13565                self.generate_date_literal(d)?;
13566            }
13567            Literal::Time(t) => {
13568                self.generate_time_literal(t)?;
13569            }
13570            Literal::Timestamp(ts) => {
13571                self.generate_timestamp_literal(ts)?;
13572            }
13573            Literal::Datetime(dt) => {
13574                self.generate_datetime_literal(dt)?;
13575            }
13576            Literal::TripleQuotedString(s, _quote_char) => {
13577                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13578                if matches!(
13579                    self.config.dialect,
13580                    Some(crate::dialects::DialectType::BigQuery)
13581                        | Some(crate::dialects::DialectType::DuckDB)
13582                        | Some(crate::dialects::DialectType::Snowflake)
13583                        | Some(crate::dialects::DialectType::Spark)
13584                        | Some(crate::dialects::DialectType::Hive)
13585                        | Some(crate::dialects::DialectType::Presto)
13586                        | Some(crate::dialects::DialectType::Trino)
13587                        | Some(crate::dialects::DialectType::PostgreSQL)
13588                        | Some(crate::dialects::DialectType::MySQL)
13589                        | Some(crate::dialects::DialectType::Redshift)
13590                        | Some(crate::dialects::DialectType::TSQL)
13591                        | Some(crate::dialects::DialectType::Oracle)
13592                        | Some(crate::dialects::DialectType::ClickHouse)
13593                        | Some(crate::dialects::DialectType::Databricks)
13594                        | Some(crate::dialects::DialectType::SQLite)
13595                ) {
13596                    self.generate_string_literal(s)?;
13597                } else {
13598                    // Preserve triple-quoted string syntax for generic/unknown dialects
13599                    let quotes = format!("{0}{0}{0}", _quote_char);
13600                    self.write(&quotes);
13601                    self.write(s);
13602                    self.write(&quotes);
13603                }
13604            }
13605            Literal::EscapeString(s) => {
13606                // PostgreSQL escape string: e'...' or E'...'
13607                // Token text format is "e:content" or "E:content"
13608                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13609                use crate::dialects::DialectType;
13610                let content = if let Some(c) = s.strip_prefix("e:") {
13611                    c
13612                } else if let Some(c) = s.strip_prefix("E:") {
13613                    c
13614                } else {
13615                    s.as_str()
13616                };
13617
13618                // MySQL: output the content without quotes or prefix
13619                if matches!(
13620                    self.config.dialect,
13621                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13622                ) {
13623                    self.write(content);
13624                } else {
13625                    // Some dialects use lowercase e' prefix
13626                    let prefix = if matches!(
13627                        self.config.dialect,
13628                        Some(DialectType::SingleStore)
13629                            | Some(DialectType::DuckDB)
13630                            | Some(DialectType::PostgreSQL)
13631                            | Some(DialectType::CockroachDB)
13632                            | Some(DialectType::Materialize)
13633                            | Some(DialectType::RisingWave)
13634                    ) {
13635                        "e'"
13636                    } else {
13637                        "E'"
13638                    };
13639
13640                    // Normalize \' to '' for output
13641                    let normalized = content.replace("\\'", "''");
13642                    self.write(prefix);
13643                    self.write(&normalized);
13644                    self.write("'");
13645                }
13646            }
13647            Literal::DollarString(s) => {
13648                // Convert dollar-quoted strings to single-quoted strings
13649                // (like Python sqlglot's rawstring_sql)
13650                use crate::dialects::DialectType;
13651                // Extract content from tag\x00content format
13652                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13653                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13654                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13655                // Step 2: Determine quote escaping style
13656                // Snowflake: ' -> \' (backslash escape)
13657                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13658                let use_backslash_quote =
13659                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13660
13661                let mut escaped = String::with_capacity(content.len() + 4);
13662                for ch in content.chars() {
13663                    if escape_backslash && ch == '\\' {
13664                        // Escape backslash first (before quote escaping)
13665                        escaped.push('\\');
13666                        escaped.push('\\');
13667                    } else if ch == '\'' {
13668                        if use_backslash_quote {
13669                            escaped.push('\\');
13670                            escaped.push('\'');
13671                        } else {
13672                            escaped.push('\'');
13673                            escaped.push('\'');
13674                        }
13675                    } else {
13676                        escaped.push(ch);
13677                    }
13678                }
13679                self.write("'");
13680                self.write(&escaped);
13681                self.write("'");
13682            }
13683            Literal::RawString(s) => {
13684                // Raw strings (r"..." or r'...') contain literal backslashes.
13685                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13686                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13687                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13688                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13689                use crate::dialects::DialectType;
13690
13691                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13692                let escape_backslash = matches!(
13693                    self.config.dialect,
13694                    Some(DialectType::BigQuery)
13695                        | Some(DialectType::MySQL)
13696                        | Some(DialectType::SingleStore)
13697                        | Some(DialectType::TiDB)
13698                        | Some(DialectType::Hive)
13699                        | Some(DialectType::Spark)
13700                        | Some(DialectType::Databricks)
13701                        | Some(DialectType::Drill)
13702                        | Some(DialectType::Snowflake)
13703                        | Some(DialectType::Redshift)
13704                        | Some(DialectType::ClickHouse)
13705                );
13706
13707                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13708                // These escape quotes as \' instead of ''
13709                let backslash_escapes_quote = matches!(
13710                    self.config.dialect,
13711                    Some(DialectType::BigQuery)
13712                        | Some(DialectType::Hive)
13713                        | Some(DialectType::Spark)
13714                        | Some(DialectType::Databricks)
13715                        | Some(DialectType::Drill)
13716                        | Some(DialectType::Snowflake)
13717                        | Some(DialectType::Redshift)
13718                );
13719
13720                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13721                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13722                let supports_escape_sequences = escape_backslash;
13723
13724                let mut escaped = String::with_capacity(s.len() + 4);
13725                for ch in s.chars() {
13726                    if escape_backslash && ch == '\\' {
13727                        // Double the backslash for the target dialect
13728                        escaped.push('\\');
13729                        escaped.push('\\');
13730                    } else if ch == '\'' {
13731                        if backslash_escapes_quote {
13732                            // Use backslash to escape the quote: \'
13733                            escaped.push('\\');
13734                            escaped.push('\'');
13735                        } else {
13736                            // Use SQL standard quote doubling: ''
13737                            escaped.push('\'');
13738                            escaped.push('\'');
13739                        }
13740                    } else if supports_escape_sequences {
13741                        // Apply ESCAPED_SEQUENCES mapping for special chars
13742                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13743                        match ch {
13744                            '\n' => {
13745                                escaped.push('\\');
13746                                escaped.push('n');
13747                            }
13748                            '\r' => {
13749                                escaped.push('\\');
13750                                escaped.push('r');
13751                            }
13752                            '\t' => {
13753                                escaped.push('\\');
13754                                escaped.push('t');
13755                            }
13756                            '\x07' => {
13757                                escaped.push('\\');
13758                                escaped.push('a');
13759                            }
13760                            '\x08' => {
13761                                escaped.push('\\');
13762                                escaped.push('b');
13763                            }
13764                            '\x0C' => {
13765                                escaped.push('\\');
13766                                escaped.push('f');
13767                            }
13768                            '\x0B' => {
13769                                escaped.push('\\');
13770                                escaped.push('v');
13771                            }
13772                            _ => escaped.push(ch),
13773                        }
13774                    } else {
13775                        escaped.push(ch);
13776                    }
13777                }
13778                self.write("'");
13779                self.write(&escaped);
13780                self.write("'");
13781            }
13782        }
13783        Ok(())
13784    }
13785
13786    /// Generate a DATE literal with dialect-specific formatting
13787    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13788        use crate::dialects::DialectType;
13789
13790        match self.config.dialect {
13791            // SQL Server uses CONVERT or CAST
13792            Some(DialectType::TSQL) => {
13793                self.write("CAST('");
13794                self.write(d);
13795                self.write("' AS DATE)");
13796            }
13797            // BigQuery uses CAST syntax for type literals
13798            // DATE 'value' -> CAST('value' AS DATE)
13799            Some(DialectType::BigQuery) => {
13800                self.write("CAST('");
13801                self.write(d);
13802                self.write("' AS DATE)");
13803            }
13804            // Exasol uses CAST syntax for DATE literals
13805            // DATE 'value' -> CAST('value' AS DATE)
13806            Some(DialectType::Exasol) => {
13807                self.write("CAST('");
13808                self.write(d);
13809                self.write("' AS DATE)");
13810            }
13811            // Snowflake uses CAST syntax for DATE literals
13812            // DATE 'value' -> CAST('value' AS DATE)
13813            Some(DialectType::Snowflake) => {
13814                self.write("CAST('");
13815                self.write(d);
13816                self.write("' AS DATE)");
13817            }
13818            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13819            Some(DialectType::PostgreSQL)
13820            | Some(DialectType::MySQL)
13821            | Some(DialectType::SingleStore)
13822            | Some(DialectType::TiDB)
13823            | Some(DialectType::Redshift) => {
13824                self.write("CAST('");
13825                self.write(d);
13826                self.write("' AS DATE)");
13827            }
13828            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13829            Some(DialectType::DuckDB)
13830            | Some(DialectType::Presto)
13831            | Some(DialectType::Trino)
13832            | Some(DialectType::Athena)
13833            | Some(DialectType::Spark)
13834            | Some(DialectType::Databricks)
13835            | Some(DialectType::Hive) => {
13836                self.write("CAST('");
13837                self.write(d);
13838                self.write("' AS DATE)");
13839            }
13840            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13841            Some(DialectType::Oracle) => {
13842                self.write("TO_DATE('");
13843                self.write(d);
13844                self.write("', 'YYYY-MM-DD')");
13845            }
13846            // Standard SQL: DATE '...'
13847            _ => {
13848                self.write_keyword("DATE");
13849                self.write(" '");
13850                self.write(d);
13851                self.write("'");
13852            }
13853        }
13854        Ok(())
13855    }
13856
13857    /// Generate a TIME literal with dialect-specific formatting
13858    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13859        use crate::dialects::DialectType;
13860
13861        match self.config.dialect {
13862            // SQL Server uses CONVERT or CAST
13863            Some(DialectType::TSQL) => {
13864                self.write("CAST('");
13865                self.write(t);
13866                self.write("' AS TIME)");
13867            }
13868            // Standard SQL: TIME '...'
13869            _ => {
13870                self.write_keyword("TIME");
13871                self.write(" '");
13872                self.write(t);
13873                self.write("'");
13874            }
13875        }
13876        Ok(())
13877    }
13878
13879    /// Generate a date expression for Dremio, converting DATE literals to CAST
13880    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13881        use crate::expressions::Literal;
13882
13883        match expr {
13884            Expression::Literal(Literal::Date(d)) => {
13885                // DATE 'value' -> CAST('value' AS DATE)
13886                self.write("CAST('");
13887                self.write(d);
13888                self.write("' AS DATE)");
13889            }
13890            _ => {
13891                // For all other expressions, generate normally
13892                self.generate_expression(expr)?;
13893            }
13894        }
13895        Ok(())
13896    }
13897
13898    /// Generate a TIMESTAMP literal with dialect-specific formatting
13899    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13900        use crate::dialects::DialectType;
13901
13902        match self.config.dialect {
13903            // SQL Server uses CONVERT or CAST
13904            Some(DialectType::TSQL) => {
13905                self.write("CAST('");
13906                self.write(ts);
13907                self.write("' AS DATETIME2)");
13908            }
13909            // BigQuery uses CAST syntax for type literals
13910            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13911            Some(DialectType::BigQuery) => {
13912                self.write("CAST('");
13913                self.write(ts);
13914                self.write("' AS TIMESTAMP)");
13915            }
13916            // Snowflake uses CAST syntax for TIMESTAMP literals
13917            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13918            Some(DialectType::Snowflake) => {
13919                self.write("CAST('");
13920                self.write(ts);
13921                self.write("' AS TIMESTAMP)");
13922            }
13923            // Dremio uses CAST syntax for TIMESTAMP literals
13924            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13925            Some(DialectType::Dremio) => {
13926                self.write("CAST('");
13927                self.write(ts);
13928                self.write("' AS TIMESTAMP)");
13929            }
13930            // Exasol uses CAST syntax for TIMESTAMP literals
13931            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13932            Some(DialectType::Exasol) => {
13933                self.write("CAST('");
13934                self.write(ts);
13935                self.write("' AS TIMESTAMP)");
13936            }
13937            // Oracle prefers TO_TIMESTAMP function call
13938            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13939            Some(DialectType::Oracle) => {
13940                self.write("TO_TIMESTAMP('");
13941                self.write(ts);
13942                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13943            }
13944            // Presto/Trino: always use CAST for TIMESTAMP literals
13945            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13946                if Self::timestamp_has_timezone(ts) {
13947                    self.write("CAST('");
13948                    self.write(ts);
13949                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13950                } else {
13951                    self.write("CAST('");
13952                    self.write(ts);
13953                    self.write("' AS TIMESTAMP)");
13954                }
13955            }
13956            // ClickHouse: CAST('...' AS Nullable(DateTime))
13957            Some(DialectType::ClickHouse) => {
13958                self.write("CAST('");
13959                self.write(ts);
13960                self.write("' AS Nullable(DateTime))");
13961            }
13962            // Spark: CAST('...' AS TIMESTAMP)
13963            Some(DialectType::Spark) => {
13964                self.write("CAST('");
13965                self.write(ts);
13966                self.write("' AS TIMESTAMP)");
13967            }
13968            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13969            // but TIMESTAMP '...' for special values like 'epoch'
13970            Some(DialectType::Redshift) => {
13971                if ts == "epoch" {
13972                    self.write_keyword("TIMESTAMP");
13973                    self.write(" '");
13974                    self.write(ts);
13975                    self.write("'");
13976                } else {
13977                    self.write("CAST('");
13978                    self.write(ts);
13979                    self.write("' AS TIMESTAMP)");
13980                }
13981            }
13982            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13983            Some(DialectType::PostgreSQL)
13984            | Some(DialectType::Hive)
13985            | Some(DialectType::SQLite)
13986            | Some(DialectType::DuckDB)
13987            | Some(DialectType::Athena)
13988            | Some(DialectType::Drill)
13989            | Some(DialectType::Teradata) => {
13990                self.write("CAST('");
13991                self.write(ts);
13992                self.write("' AS TIMESTAMP)");
13993            }
13994            // MySQL/StarRocks: CAST('...' AS DATETIME)
13995            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13996                self.write("CAST('");
13997                self.write(ts);
13998                self.write("' AS DATETIME)");
13999            }
14000            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14001            Some(DialectType::Databricks) => {
14002                self.write("CAST('");
14003                self.write(ts);
14004                self.write("' AS TIMESTAMP_NTZ)");
14005            }
14006            // Standard SQL: TIMESTAMP '...'
14007            _ => {
14008                self.write_keyword("TIMESTAMP");
14009                self.write(" '");
14010                self.write(ts);
14011                self.write("'");
14012            }
14013        }
14014        Ok(())
14015    }
14016
14017    /// Check if a timestamp string contains a timezone identifier
14018    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14019    fn timestamp_has_timezone(ts: &str) -> bool {
14020        // Check for common IANA timezone patterns: Continent/City format
14021        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14022        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14023        let ts_lower = ts.to_lowercase();
14024
14025        // Check for Continent/City pattern (most common)
14026        let continent_prefixes = [
14027            "africa/",
14028            "america/",
14029            "antarctica/",
14030            "arctic/",
14031            "asia/",
14032            "atlantic/",
14033            "australia/",
14034            "europe/",
14035            "indian/",
14036            "pacific/",
14037            "etc/",
14038            "brazil/",
14039            "canada/",
14040            "chile/",
14041            "mexico/",
14042            "us/",
14043        ];
14044
14045        for prefix in &continent_prefixes {
14046            if ts_lower.contains(prefix) {
14047                return true;
14048            }
14049        }
14050
14051        // Check for standalone timezone abbreviations at the end
14052        // These typically appear after the time portion
14053        let tz_abbrevs = [
14054            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14055            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14056            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14057        ];
14058
14059        for abbrev in &tz_abbrevs {
14060            if ts_lower.ends_with(abbrev) {
14061                return true;
14062            }
14063        }
14064
14065        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14066        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14067        // Look for pattern: space followed by + or - and digits (optionally with :)
14068        let trimmed = ts.trim();
14069        if let Some(last_space) = trimmed.rfind(' ') {
14070            let suffix = &trimmed[last_space + 1..];
14071            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14072                // Check if rest is numeric (possibly with : for hh:mm format)
14073                let rest = &suffix[1..];
14074                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14075                    return true;
14076                }
14077            }
14078        }
14079
14080        false
14081    }
14082
14083    /// Generate a DATETIME literal with dialect-specific formatting
14084    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14085        use crate::dialects::DialectType;
14086
14087        match self.config.dialect {
14088            // BigQuery uses CAST syntax for type literals
14089            // DATETIME 'value' -> CAST('value' AS DATETIME)
14090            Some(DialectType::BigQuery) => {
14091                self.write("CAST('");
14092                self.write(dt);
14093                self.write("' AS DATETIME)");
14094            }
14095            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14096            Some(DialectType::DuckDB) => {
14097                self.write("CAST('");
14098                self.write(dt);
14099                self.write("' AS TIMESTAMP)");
14100            }
14101            // DATETIME is primarily a BigQuery type
14102            // Output as DATETIME '...' for dialects that support it
14103            _ => {
14104                self.write_keyword("DATETIME");
14105                self.write(" '");
14106                self.write(dt);
14107                self.write("'");
14108            }
14109        }
14110        Ok(())
14111    }
14112
14113    /// Generate a string literal with dialect-specific escaping
14114    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14115        use crate::dialects::DialectType;
14116
14117        match self.config.dialect {
14118            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14119            // and backslash escaping for special characters like newlines
14120            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14121            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14122                // Hive/Spark use backslash escaping for quotes (\') and special chars
14123                self.write("'");
14124                for c in s.chars() {
14125                    match c {
14126                        '\'' => self.write("\\'"),
14127                        '\\' => self.write("\\\\"),
14128                        '\n' => self.write("\\n"),
14129                        '\r' => self.write("\\r"),
14130                        '\t' => self.write("\\t"),
14131                        '\0' => self.write("\\0"),
14132                        _ => self.output.push(c),
14133                    }
14134                }
14135                self.write("'");
14136            }
14137            Some(DialectType::Drill) => {
14138                // Drill uses SQL-standard quote doubling ('') for quotes,
14139                // but backslash escaping for special characters
14140                self.write("'");
14141                for c in s.chars() {
14142                    match c {
14143                        '\'' => self.write("''"),
14144                        '\\' => self.write("\\\\"),
14145                        '\n' => self.write("\\n"),
14146                        '\r' => self.write("\\r"),
14147                        '\t' => self.write("\\t"),
14148                        '\0' => self.write("\\0"),
14149                        _ => self.output.push(c),
14150                    }
14151                }
14152                self.write("'");
14153            }
14154            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14155                self.write("'");
14156                for c in s.chars() {
14157                    match c {
14158                        // MySQL uses SQL standard quote doubling
14159                        '\'' => self.write("''"),
14160                        '\\' => self.write("\\\\"),
14161                        '\n' => self.write("\\n"),
14162                        '\r' => self.write("\\r"),
14163                        '\t' => self.write("\\t"),
14164                        // sqlglot writes a literal NUL for this case
14165                        '\0' => self.output.push('\0'),
14166                        _ => self.output.push(c),
14167                    }
14168                }
14169                self.write("'");
14170            }
14171            // BigQuery: Uses backslash escaping
14172            Some(DialectType::BigQuery) => {
14173                self.write("'");
14174                for c in s.chars() {
14175                    match c {
14176                        '\'' => self.write("\\'"),
14177                        '\\' => self.write("\\\\"),
14178                        '\n' => self.write("\\n"),
14179                        '\r' => self.write("\\r"),
14180                        '\t' => self.write("\\t"),
14181                        '\0' => self.write("\\0"),
14182                        '\x07' => self.write("\\a"),
14183                        '\x08' => self.write("\\b"),
14184                        '\x0C' => self.write("\\f"),
14185                        '\x0B' => self.write("\\v"),
14186                        _ => self.output.push(c),
14187                    }
14188                }
14189                self.write("'");
14190            }
14191            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14192            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14193            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14194            Some(DialectType::Athena) => {
14195                if self.athena_hive_context {
14196                    // Hive-style: backslash escaping
14197                    self.write("'");
14198                    for c in s.chars() {
14199                        match c {
14200                            '\'' => self.write("\\'"),
14201                            '\\' => self.write("\\\\"),
14202                            '\n' => self.write("\\n"),
14203                            '\r' => self.write("\\r"),
14204                            '\t' => self.write("\\t"),
14205                            '\0' => self.write("\\0"),
14206                            _ => self.output.push(c),
14207                        }
14208                    }
14209                    self.write("'");
14210                } else {
14211                    // Trino-style: SQL-standard escaping, preserve backslashes
14212                    self.write("'");
14213                    for c in s.chars() {
14214                        match c {
14215                            '\'' => self.write("''"),
14216                            // Preserve backslashes literally (no re-escaping)
14217                            _ => self.output.push(c),
14218                        }
14219                    }
14220                    self.write("'");
14221                }
14222            }
14223            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14224            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14225            // becomes string value '\\'), so we should NOT re-escape backslashes.
14226            // We only need to escape single quotes.
14227            Some(DialectType::Snowflake) => {
14228                self.write("'");
14229                for c in s.chars() {
14230                    match c {
14231                        '\'' => self.write("\\'"),
14232                        // Backslashes are already escaped in the tokenized string, don't re-escape
14233                        // Only escape special characters that might not have been escaped
14234                        '\n' => self.write("\\n"),
14235                        '\r' => self.write("\\r"),
14236                        '\t' => self.write("\\t"),
14237                        _ => self.output.push(c),
14238                    }
14239                }
14240                self.write("'");
14241            }
14242            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14243            Some(DialectType::PostgreSQL) => {
14244                self.write("'");
14245                for c in s.chars() {
14246                    match c {
14247                        '\'' => self.write("''"),
14248                        _ => self.output.push(c),
14249                    }
14250                }
14251                self.write("'");
14252            }
14253            // Redshift: Uses backslash escaping for single quotes
14254            Some(DialectType::Redshift) => {
14255                self.write("'");
14256                for c in s.chars() {
14257                    match c {
14258                        '\'' => self.write("\\'"),
14259                        _ => self.output.push(c),
14260                    }
14261                }
14262                self.write("'");
14263            }
14264            // Oracle: Uses standard double single-quote escaping
14265            Some(DialectType::Oracle) => {
14266                self.write("'");
14267                self.write(&s.replace('\'', "''"));
14268                self.write("'");
14269            }
14270            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14271            // backslash escaping for backslashes and special characters
14272            Some(DialectType::ClickHouse) => {
14273                self.write("'");
14274                for c in s.chars() {
14275                    match c {
14276                        '\'' => self.write("''"),
14277                        '\\' => self.write("\\\\"),
14278                        '\n' => self.write("\\n"),
14279                        '\r' => self.write("\\r"),
14280                        '\t' => self.write("\\t"),
14281                        '\0' => self.write("\\0"),
14282                        '\x07' => self.write("\\a"),
14283                        '\x08' => self.write("\\b"),
14284                        '\x0C' => self.write("\\f"),
14285                        '\x0B' => self.write("\\v"),
14286                        // Non-printable characters: emit as \xNN hex escapes
14287                        c if c.is_control() || (c as u32) < 0x20 => {
14288                            let byte = c as u32;
14289                            if byte < 256 {
14290                                self.write(&format!("\\x{:02X}", byte));
14291                            } else {
14292                                self.output.push(c);
14293                            }
14294                        }
14295                        _ => self.output.push(c),
14296                    }
14297                }
14298                self.write("'");
14299            }
14300            // Default: SQL standard double single quotes (works for most dialects)
14301            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14302            _ => {
14303                self.write("'");
14304                self.write(&s.replace('\'', "''"));
14305                self.write("'");
14306            }
14307        }
14308        Ok(())
14309    }
14310
14311    /// Write a byte string with proper escaping for BigQuery-style byte literals
14312    /// Escapes characters as \xNN hex escapes where needed
14313    fn write_escaped_byte_string(&mut self, s: &str) {
14314        for c in s.chars() {
14315            match c {
14316                // Escape single quotes
14317                '\'' => self.write("\\'"),
14318                // Escape backslashes
14319                '\\' => self.write("\\\\"),
14320                // Keep all printable characters (including non-ASCII) as-is
14321                _ if !c.is_control() => self.output.push(c),
14322                // Escape control characters as hex
14323                _ => {
14324                    let byte = c as u32;
14325                    if byte < 256 {
14326                        self.write(&format!("\\x{:02x}", byte));
14327                    } else {
14328                        // For unicode characters, write each UTF-8 byte
14329                        for b in c.to_string().as_bytes() {
14330                            self.write(&format!("\\x{:02x}", b));
14331                        }
14332                    }
14333                }
14334            }
14335        }
14336    }
14337
14338    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14339        use crate::dialects::DialectType;
14340
14341        // Different dialects have different boolean literal formats
14342        match self.config.dialect {
14343            // SQL Server typically uses 1/0 for boolean literals in many contexts
14344            // However, TRUE/FALSE also works in modern versions
14345            Some(DialectType::TSQL) => {
14346                self.write(if b.value { "1" } else { "0" });
14347            }
14348            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14349            Some(DialectType::Oracle) => {
14350                self.write(if b.value { "1" } else { "0" });
14351            }
14352            // MySQL accepts TRUE/FALSE as aliases for 1/0
14353            Some(DialectType::MySQL) => {
14354                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14355            }
14356            // Most other dialects support TRUE/FALSE
14357            _ => {
14358                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14359            }
14360        }
14361        Ok(())
14362    }
14363
14364    /// Generate an identifier that's used as an alias name
14365    /// This quotes reserved keywords in addition to already-quoted identifiers
14366    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14367        let name = &id.name;
14368        let quote_style = &self.config.identifier_quote_style;
14369
14370        // For aliases, quote if:
14371        // 1. The identifier was explicitly quoted in the source
14372        // 2. The identifier is a reserved keyword for the current dialect
14373        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14374
14375        // Normalize identifier if configured
14376        let output_name = if self.config.normalize_identifiers && !id.quoted {
14377            name.to_lowercase()
14378        } else {
14379            name.to_string()
14380        };
14381
14382        if needs_quoting {
14383            // Escape any quote characters within the identifier
14384            let escaped_name = if quote_style.start == quote_style.end {
14385                output_name.replace(
14386                    quote_style.end,
14387                    &format!("{}{}", quote_style.end, quote_style.end),
14388                )
14389            } else {
14390                output_name.replace(
14391                    quote_style.end,
14392                    &format!("{}{}", quote_style.end, quote_style.end),
14393                )
14394            };
14395            self.write(&format!(
14396                "{}{}{}",
14397                quote_style.start, escaped_name, quote_style.end
14398            ));
14399        } else {
14400            self.write(&output_name);
14401        }
14402
14403        // Output trailing comments
14404        for comment in &id.trailing_comments {
14405            self.write(" ");
14406            self.write_formatted_comment(comment);
14407        }
14408        Ok(())
14409    }
14410
14411    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14412        use crate::dialects::DialectType;
14413
14414        let name = &id.name;
14415
14416        // For Athena, use backticks in Hive context, double quotes in Trino context
14417        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14418            && self.athena_hive_context
14419        {
14420            &IdentifierQuoteStyle::BACKTICK
14421        } else {
14422            &self.config.identifier_quote_style
14423        };
14424
14425        // Quote if:
14426        // 1. The identifier was explicitly quoted in the source
14427        // 2. The identifier is a reserved keyword for the current dialect
14428        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14429        // This matches Python sqlglot's identifier_sql behavior
14430        // Also quote identifiers starting with digits if the target dialect doesn't support them
14431        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14432        let needs_digit_quoting = starts_with_digit
14433            && !self.config.identifiers_can_start_with_digit
14434            && self.config.dialect.is_some();
14435        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14436            && name.len() > 2
14437            && (name.starts_with("0x") || name.starts_with("0X"))
14438            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14439        let needs_quoting = id.quoted
14440            || self.is_reserved_keyword(name)
14441            || self.config.always_quote_identifiers
14442            || needs_digit_quoting
14443            || mysql_invalid_hex_identifier;
14444
14445        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14446        // When quoted, we need to output `name`(16) not `name(16)`
14447        let (base_name, suffix) = if needs_quoting {
14448            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14449            if let Some(paren_pos) = name.find('(') {
14450                let base = &name[..paren_pos];
14451                let rest = &name[paren_pos..];
14452                // Verify it looks like (digits) or (digits) ASC/DESC
14453                if rest.starts_with('(')
14454                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14455                {
14456                    // Check if content between parens is all digits
14457                    let close_paren = rest.find(')').unwrap_or(rest.len());
14458                    let inside = &rest[1..close_paren];
14459                    if inside.chars().all(|c| c.is_ascii_digit()) {
14460                        (base.to_string(), rest.to_string())
14461                    } else {
14462                        (name.to_string(), String::new())
14463                    }
14464                } else {
14465                    (name.to_string(), String::new())
14466                }
14467            } else if name.ends_with(" ASC") {
14468                let base = &name[..name.len() - 4];
14469                (base.to_string(), " ASC".to_string())
14470            } else if name.ends_with(" DESC") {
14471                let base = &name[..name.len() - 5];
14472                (base.to_string(), " DESC".to_string())
14473            } else {
14474                (name.to_string(), String::new())
14475            }
14476        } else {
14477            (name.to_string(), String::new())
14478        };
14479
14480        // Normalize identifier if configured, with special handling for Exasol
14481        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14482        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14483        let output_name = if self.config.normalize_identifiers && !id.quoted {
14484            base_name.to_lowercase()
14485        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14486            && !id.quoted
14487            && self.is_reserved_keyword(name)
14488        {
14489            // Exasol: uppercase reserved keywords when quoting them
14490            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14491            base_name.to_uppercase()
14492        } else {
14493            base_name
14494        };
14495
14496        if needs_quoting {
14497            // Escape any quote characters within the identifier
14498            let escaped_name = if quote_style.start == quote_style.end {
14499                // Same start/end char (e.g., " or `) - double the quote char
14500                output_name.replace(
14501                    quote_style.end,
14502                    &format!("{}{}", quote_style.end, quote_style.end),
14503                )
14504            } else {
14505                // Different start/end (e.g., [ and ]) - escape only the end char
14506                output_name.replace(
14507                    quote_style.end,
14508                    &format!("{}{}", quote_style.end, quote_style.end),
14509                )
14510            };
14511            self.write(&format!(
14512                "{}{}{}{}",
14513                quote_style.start, escaped_name, quote_style.end, suffix
14514            ));
14515        } else {
14516            self.write(&output_name);
14517        }
14518
14519        // Output trailing comments
14520        for comment in &id.trailing_comments {
14521            self.write(" ");
14522            self.write_formatted_comment(comment);
14523        }
14524        Ok(())
14525    }
14526
14527    fn generate_column(&mut self, col: &Column) -> Result<()> {
14528        use crate::dialects::DialectType;
14529
14530        if let Some(table) = &col.table {
14531            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14532            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14533            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14534            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14535                && !table.quoted
14536                && table.name.eq_ignore_ascii_case("LOCAL");
14537
14538            if is_exasol_local_prefix {
14539                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14540                self.write("LOCAL");
14541            } else {
14542                self.generate_identifier(table)?;
14543            }
14544            self.write(".");
14545        }
14546        self.generate_identifier(&col.name)?;
14547        // Oracle-style join marker (+)
14548        // Only output if dialect supports it (Oracle, Exasol)
14549        if col.join_mark && self.config.supports_column_join_marks {
14550            self.write(" (+)");
14551        }
14552        // Output trailing comments
14553        for comment in &col.trailing_comments {
14554            self.write_space();
14555            self.write_formatted_comment(comment);
14556        }
14557        Ok(())
14558    }
14559
14560    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14561    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14562    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14563        use crate::dialects::DialectType;
14564        use crate::expressions::PseudocolumnType;
14565
14566        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14567        if pc.kind == PseudocolumnType::Sysdate
14568            && !matches!(
14569                self.config.dialect,
14570                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14571            )
14572        {
14573            self.write_keyword("CURRENT_TIMESTAMP");
14574            // Add () for dialects that expect it
14575            if matches!(
14576                self.config.dialect,
14577                Some(DialectType::MySQL)
14578                    | Some(DialectType::ClickHouse)
14579                    | Some(DialectType::Spark)
14580                    | Some(DialectType::Databricks)
14581                    | Some(DialectType::Hive)
14582            ) {
14583                self.write("()");
14584            }
14585        } else {
14586            self.write(pc.kind.as_str());
14587        }
14588        Ok(())
14589    }
14590
14591    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14592    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14593        use crate::dialects::DialectType;
14594
14595        // Generate native CONNECT BY for Oracle and Snowflake
14596        // For other dialects, add a comment noting manual conversion needed
14597        let supports_connect_by = matches!(
14598            self.config.dialect,
14599            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14600        );
14601
14602        if !supports_connect_by && self.config.dialect.is_some() {
14603            // Add comment for unsupported dialects
14604            if self.config.pretty {
14605                self.write_newline();
14606            } else {
14607                self.write_space();
14608            }
14609            self.write_unsupported_comment(
14610                "CONNECT BY requires manual conversion to recursive CTE",
14611            )?;
14612        }
14613
14614        // Generate START WITH if present (before CONNECT BY)
14615        if let Some(start) = &connect.start {
14616            if self.config.pretty {
14617                self.write_newline();
14618            } else {
14619                self.write_space();
14620            }
14621            self.write_keyword("START WITH");
14622            self.write_space();
14623            self.generate_expression(start)?;
14624        }
14625
14626        // Generate CONNECT BY
14627        if self.config.pretty {
14628            self.write_newline();
14629        } else {
14630            self.write_space();
14631        }
14632        self.write_keyword("CONNECT BY");
14633        if connect.nocycle {
14634            self.write_space();
14635            self.write_keyword("NOCYCLE");
14636        }
14637        self.write_space();
14638        self.generate_expression(&connect.connect)?;
14639
14640        Ok(())
14641    }
14642
14643    /// Generate Connect expression (for Expression::Connect variant)
14644    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14645        self.generate_connect(connect)
14646    }
14647
14648    /// Generate PRIOR expression
14649    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14650        self.write_keyword("PRIOR");
14651        self.write_space();
14652        self.generate_expression(&prior.this)?;
14653        Ok(())
14654    }
14655
14656    /// Generate CONNECT_BY_ROOT function
14657    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14658    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14659        self.write_keyword("CONNECT_BY_ROOT");
14660        self.write_space();
14661        self.generate_expression(&cbr.this)?;
14662        Ok(())
14663    }
14664
14665    /// Generate MATCH_RECOGNIZE clause
14666    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14667        use crate::dialects::DialectType;
14668
14669        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14670        let supports_match_recognize = matches!(
14671            self.config.dialect,
14672            Some(DialectType::Oracle)
14673                | Some(DialectType::Snowflake)
14674                | Some(DialectType::Presto)
14675                | Some(DialectType::Trino)
14676        );
14677
14678        // Generate the source table first
14679        if let Some(source) = &mr.this {
14680            self.generate_expression(source)?;
14681        }
14682
14683        if !supports_match_recognize {
14684            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
14685            return Ok(());
14686        }
14687
14688        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14689        if self.config.pretty {
14690            self.write_newline();
14691        } else {
14692            self.write_space();
14693        }
14694
14695        self.write_keyword("MATCH_RECOGNIZE");
14696        self.write(" (");
14697
14698        if self.config.pretty {
14699            self.indent_level += 1;
14700        }
14701
14702        let mut needs_separator = false;
14703
14704        // PARTITION BY
14705        if let Some(partition_by) = &mr.partition_by {
14706            if !partition_by.is_empty() {
14707                if self.config.pretty {
14708                    self.write_newline();
14709                    self.write_indent();
14710                }
14711                self.write_keyword("PARTITION BY");
14712                self.write_space();
14713                for (i, expr) in partition_by.iter().enumerate() {
14714                    if i > 0 {
14715                        self.write(", ");
14716                    }
14717                    self.generate_expression(expr)?;
14718                }
14719                needs_separator = true;
14720            }
14721        }
14722
14723        // ORDER BY
14724        if let Some(order_by) = &mr.order_by {
14725            if !order_by.is_empty() {
14726                if needs_separator {
14727                    if self.config.pretty {
14728                        self.write_newline();
14729                        self.write_indent();
14730                    } else {
14731                        self.write_space();
14732                    }
14733                } else if self.config.pretty {
14734                    self.write_newline();
14735                    self.write_indent();
14736                }
14737                self.write_keyword("ORDER BY");
14738                // In pretty mode, put each ORDER BY column on a new indented line
14739                if self.config.pretty {
14740                    self.indent_level += 1;
14741                    for (i, ordered) in order_by.iter().enumerate() {
14742                        if i > 0 {
14743                            self.write(",");
14744                        }
14745                        self.write_newline();
14746                        self.write_indent();
14747                        self.generate_ordered(ordered)?;
14748                    }
14749                    self.indent_level -= 1;
14750                } else {
14751                    self.write_space();
14752                    for (i, ordered) in order_by.iter().enumerate() {
14753                        if i > 0 {
14754                            self.write(", ");
14755                        }
14756                        self.generate_ordered(ordered)?;
14757                    }
14758                }
14759                needs_separator = true;
14760            }
14761        }
14762
14763        // MEASURES
14764        if let Some(measures) = &mr.measures {
14765            if !measures.is_empty() {
14766                if needs_separator {
14767                    if self.config.pretty {
14768                        self.write_newline();
14769                        self.write_indent();
14770                    } else {
14771                        self.write_space();
14772                    }
14773                } else if self.config.pretty {
14774                    self.write_newline();
14775                    self.write_indent();
14776                }
14777                self.write_keyword("MEASURES");
14778                // In pretty mode, put each MEASURE on a new indented line
14779                if self.config.pretty {
14780                    self.indent_level += 1;
14781                    for (i, measure) in measures.iter().enumerate() {
14782                        if i > 0 {
14783                            self.write(",");
14784                        }
14785                        self.write_newline();
14786                        self.write_indent();
14787                        // Handle RUNNING/FINAL prefix
14788                        if let Some(semantics) = &measure.window_frame {
14789                            match semantics {
14790                                MatchRecognizeSemantics::Running => {
14791                                    self.write_keyword("RUNNING");
14792                                    self.write_space();
14793                                }
14794                                MatchRecognizeSemantics::Final => {
14795                                    self.write_keyword("FINAL");
14796                                    self.write_space();
14797                                }
14798                            }
14799                        }
14800                        self.generate_expression(&measure.this)?;
14801                    }
14802                    self.indent_level -= 1;
14803                } else {
14804                    self.write_space();
14805                    for (i, measure) in measures.iter().enumerate() {
14806                        if i > 0 {
14807                            self.write(", ");
14808                        }
14809                        // Handle RUNNING/FINAL prefix
14810                        if let Some(semantics) = &measure.window_frame {
14811                            match semantics {
14812                                MatchRecognizeSemantics::Running => {
14813                                    self.write_keyword("RUNNING");
14814                                    self.write_space();
14815                                }
14816                                MatchRecognizeSemantics::Final => {
14817                                    self.write_keyword("FINAL");
14818                                    self.write_space();
14819                                }
14820                            }
14821                        }
14822                        self.generate_expression(&measure.this)?;
14823                    }
14824                }
14825                needs_separator = true;
14826            }
14827        }
14828
14829        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14830        if let Some(rows) = &mr.rows {
14831            if needs_separator {
14832                if self.config.pretty {
14833                    self.write_newline();
14834                    self.write_indent();
14835                } else {
14836                    self.write_space();
14837                }
14838            } else if self.config.pretty {
14839                self.write_newline();
14840                self.write_indent();
14841            }
14842            match rows {
14843                MatchRecognizeRows::OneRowPerMatch => {
14844                    self.write_keyword("ONE ROW PER MATCH");
14845                }
14846                MatchRecognizeRows::AllRowsPerMatch => {
14847                    self.write_keyword("ALL ROWS PER MATCH");
14848                }
14849                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14850                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14851                }
14852                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14853                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14854                }
14855                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14856                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14857                }
14858            }
14859            needs_separator = true;
14860        }
14861
14862        // AFTER MATCH SKIP
14863        if let Some(after) = &mr.after {
14864            if needs_separator {
14865                if self.config.pretty {
14866                    self.write_newline();
14867                    self.write_indent();
14868                } else {
14869                    self.write_space();
14870                }
14871            } else if self.config.pretty {
14872                self.write_newline();
14873                self.write_indent();
14874            }
14875            match after {
14876                MatchRecognizeAfter::PastLastRow => {
14877                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14878                }
14879                MatchRecognizeAfter::ToNextRow => {
14880                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14881                }
14882                MatchRecognizeAfter::ToFirst(ident) => {
14883                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14884                    self.write_space();
14885                    self.generate_identifier(ident)?;
14886                }
14887                MatchRecognizeAfter::ToLast(ident) => {
14888                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14889                    self.write_space();
14890                    self.generate_identifier(ident)?;
14891                }
14892            }
14893            needs_separator = true;
14894        }
14895
14896        // PATTERN
14897        if let Some(pattern) = &mr.pattern {
14898            if needs_separator {
14899                if self.config.pretty {
14900                    self.write_newline();
14901                    self.write_indent();
14902                } else {
14903                    self.write_space();
14904                }
14905            } else if self.config.pretty {
14906                self.write_newline();
14907                self.write_indent();
14908            }
14909            self.write_keyword("PATTERN");
14910            self.write_space();
14911            self.write("(");
14912            self.write(pattern);
14913            self.write(")");
14914            needs_separator = true;
14915        }
14916
14917        // DEFINE
14918        if let Some(define) = &mr.define {
14919            if !define.is_empty() {
14920                if needs_separator {
14921                    if self.config.pretty {
14922                        self.write_newline();
14923                        self.write_indent();
14924                    } else {
14925                        self.write_space();
14926                    }
14927                } else if self.config.pretty {
14928                    self.write_newline();
14929                    self.write_indent();
14930                }
14931                self.write_keyword("DEFINE");
14932                // In pretty mode, put each DEFINE on a new indented line
14933                if self.config.pretty {
14934                    self.indent_level += 1;
14935                    for (i, (name, expr)) in define.iter().enumerate() {
14936                        if i > 0 {
14937                            self.write(",");
14938                        }
14939                        self.write_newline();
14940                        self.write_indent();
14941                        self.generate_identifier(name)?;
14942                        self.write(" AS ");
14943                        self.generate_expression(expr)?;
14944                    }
14945                    self.indent_level -= 1;
14946                } else {
14947                    self.write_space();
14948                    for (i, (name, expr)) in define.iter().enumerate() {
14949                        if i > 0 {
14950                            self.write(", ");
14951                        }
14952                        self.generate_identifier(name)?;
14953                        self.write(" AS ");
14954                        self.generate_expression(expr)?;
14955                    }
14956                }
14957            }
14958        }
14959
14960        if self.config.pretty {
14961            self.indent_level -= 1;
14962            self.write_newline();
14963        }
14964        self.write(")");
14965
14966        // Alias - only include AS if it was explicitly present in the input
14967        if let Some(alias) = &mr.alias {
14968            self.write(" ");
14969            if mr.alias_explicit_as {
14970                self.write_keyword("AS");
14971                self.write(" ");
14972            }
14973            self.generate_identifier(alias)?;
14974        }
14975
14976        Ok(())
14977    }
14978
14979    /// Generate a query hint /*+ ... */
14980    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14981        use crate::dialects::DialectType;
14982
14983        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14984        let supports_hints = matches!(
14985            self.config.dialect,
14986            None |  // No dialect = preserve everything
14987            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14988            Some(DialectType::Spark) | Some(DialectType::Hive) |
14989            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14990        );
14991
14992        if !supports_hints || hint.expressions.is_empty() {
14993            return Ok(());
14994        }
14995
14996        // First, expand raw hint text into individual hint strings
14997        // This handles the case where the parser stored multiple hints as a single raw string
14998        let mut hint_strings: Vec<String> = Vec::new();
14999        for expr in &hint.expressions {
15000            match expr {
15001                HintExpression::Raw(text) => {
15002                    // Parse raw hint text into individual hint function calls
15003                    let parsed = self.parse_raw_hint_text(text);
15004                    hint_strings.extend(parsed);
15005                }
15006                _ => {
15007                    hint_strings.push(self.hint_expression_to_string(expr)?);
15008                }
15009            }
15010        }
15011
15012        // In pretty mode with multiple hints, always use multiline format
15013        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15014        // always joins with newlines in pretty mode
15015        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15016
15017        if use_multiline {
15018            // Pretty print with each hint on its own line
15019            self.write(" /*+ ");
15020            for (i, hint_str) in hint_strings.iter().enumerate() {
15021                if i > 0 {
15022                    self.write_newline();
15023                    self.write("  "); // 2-space indent within hint block
15024                }
15025                self.write(hint_str);
15026            }
15027            self.write(" */");
15028        } else {
15029            // Single line format
15030            self.write(" /*+ ");
15031            let sep = match self.config.dialect {
15032                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15033                _ => " ",
15034            };
15035            for (i, hint_str) in hint_strings.iter().enumerate() {
15036                if i > 0 {
15037                    self.write(sep);
15038                }
15039                self.write(hint_str);
15040            }
15041            self.write(" */");
15042        }
15043
15044        Ok(())
15045    }
15046
15047    /// Parse raw hint text into individual hint function calls
15048    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15049    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15050    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15051        let mut results = Vec::new();
15052        let mut chars = text.chars().peekable();
15053        let mut current = String::new();
15054        let mut paren_depth = 0;
15055        let mut has_unparseable_content = false;
15056        let mut position_after_last_function = 0;
15057        let mut char_position = 0;
15058
15059        while let Some(c) = chars.next() {
15060            char_position += c.len_utf8();
15061            match c {
15062                '(' => {
15063                    paren_depth += 1;
15064                    current.push(c);
15065                }
15066                ')' => {
15067                    paren_depth -= 1;
15068                    current.push(c);
15069                    // When we close the outer parenthesis, we've completed a hint function
15070                    if paren_depth == 0 {
15071                        let trimmed = current.trim().to_string();
15072                        if !trimmed.is_empty() {
15073                            // Format this hint for pretty printing if needed
15074                            let formatted = self.format_hint_function(&trimmed);
15075                            results.push(formatted);
15076                        }
15077                        current.clear();
15078                        position_after_last_function = char_position;
15079                    }
15080                }
15081                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15082                    // Space/comma/whitespace outside parentheses - skip
15083                }
15084                _ if paren_depth == 0 => {
15085                    // Character outside parentheses - accumulate for potential hint name
15086                    current.push(c);
15087                }
15088                _ => {
15089                    current.push(c);
15090                }
15091            }
15092        }
15093
15094        // Check if there's remaining text after the last function call
15095        let remaining_text = text[position_after_last_function..].trim();
15096        if !remaining_text.is_empty() {
15097            // Check if it looks like valid hint function names
15098            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15099            // If we see multiple words without parens, it's likely unparseable
15100            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15101            let looks_like_hint_functions = words.iter().all(|word| {
15102                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15103                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15104            });
15105
15106            if !looks_like_hint_functions && words.len() > 1 {
15107                has_unparseable_content = true;
15108            }
15109        }
15110
15111        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15112        if has_unparseable_content {
15113            return vec![text.trim().to_string()];
15114        }
15115
15116        // If we couldn't parse anything, return the original text as a single hint
15117        if results.is_empty() {
15118            results.push(text.trim().to_string());
15119        }
15120
15121        results
15122    }
15123
15124    /// Format a hint function for pretty printing
15125    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15126    fn format_hint_function(&self, hint: &str) -> String {
15127        if !self.config.pretty {
15128            return hint.to_string();
15129        }
15130
15131        // Try to parse NAME(args) pattern
15132        if let Some(paren_pos) = hint.find('(') {
15133            if hint.ends_with(')') {
15134                let name = &hint[..paren_pos];
15135                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15136
15137                // Parse arguments (space-separated for Oracle hints)
15138                let args: Vec<&str> = args_str.split_whitespace().collect();
15139
15140                // Calculate total width of arguments
15141                let total_args_width: usize =
15142                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15143
15144                // If too wide, format on multiple lines
15145                if total_args_width > self.config.max_text_width && !args.is_empty() {
15146                    let mut result = format!("{}(\n", name);
15147                    for arg in &args {
15148                        result.push_str("    "); // 4-space indent for args
15149                        result.push_str(arg);
15150                        result.push('\n');
15151                    }
15152                    result.push_str("  )"); // 2-space indent for closing paren
15153                    return result;
15154                }
15155            }
15156        }
15157
15158        hint.to_string()
15159    }
15160
15161    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15162    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15163        match expr {
15164            HintExpression::Function { name, args } => {
15165                // Generate each argument to a string
15166                let arg_strings: Vec<String> = args
15167                    .iter()
15168                    .map(|arg| {
15169                        let mut gen = Generator::with_config(self.config.clone());
15170                        gen.generate_expression(arg)?;
15171                        Ok(gen.output)
15172                    })
15173                    .collect::<Result<Vec<_>>>()?;
15174
15175                // Oracle hints use space-separated arguments, not comma-separated
15176                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15177                    + arg_strings.len().saturating_sub(1); // spaces between args
15178
15179                // Check if function args need multiline formatting
15180                // Use too_wide check for argument formatting
15181                let args_multiline =
15182                    self.config.pretty && total_args_width > self.config.max_text_width;
15183
15184                if args_multiline && !arg_strings.is_empty() {
15185                    // Multiline format for long argument lists
15186                    let mut result = format!("{}(\n", name);
15187                    for arg_str in &arg_strings {
15188                        result.push_str("    "); // 4-space indent for args
15189                        result.push_str(arg_str);
15190                        result.push('\n');
15191                    }
15192                    result.push_str("  )"); // 2-space indent for closing paren
15193                    Ok(result)
15194                } else {
15195                    // Single line format with space-separated args (Oracle style)
15196                    let args_str = arg_strings.join(" ");
15197                    Ok(format!("{}({})", name, args_str))
15198                }
15199            }
15200            HintExpression::Identifier(name) => Ok(name.clone()),
15201            HintExpression::Raw(text) => {
15202                // For pretty printing, try to format the raw text
15203                if self.config.pretty {
15204                    Ok(self.format_hint_function(text))
15205                } else {
15206                    Ok(text.clone())
15207                }
15208            }
15209        }
15210    }
15211
15212    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15213        // PostgreSQL ONLY modifier: prevents scanning child tables
15214        if table.only {
15215            self.write_keyword("ONLY");
15216            self.write_space();
15217        }
15218
15219        // Check for Snowflake IDENTIFIER() function
15220        if let Some(ref identifier_func) = table.identifier_func {
15221            self.generate_expression(identifier_func)?;
15222        } else {
15223            if let Some(catalog) = &table.catalog {
15224                self.generate_identifier(catalog)?;
15225                self.write(".");
15226            }
15227            if let Some(schema) = &table.schema {
15228                self.generate_identifier(schema)?;
15229                self.write(".");
15230            }
15231            self.generate_identifier(&table.name)?;
15232        }
15233
15234        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15235        if let Some(changes) = &table.changes {
15236            self.write(" ");
15237            self.generate_changes(changes)?;
15238        }
15239
15240        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15241        if !table.partitions.is_empty() {
15242            self.write_space();
15243            self.write_keyword("PARTITION");
15244            self.write("(");
15245            for (i, partition) in table.partitions.iter().enumerate() {
15246                if i > 0 {
15247                    self.write(", ");
15248                }
15249                self.generate_identifier(partition)?;
15250            }
15251            self.write(")");
15252        }
15253
15254        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15255        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15256        if table.changes.is_none() {
15257            if let Some(when) = &table.when {
15258                self.write_space();
15259                self.generate_historical_data(when)?;
15260            }
15261        }
15262
15263        // Output TSQL FOR SYSTEM_TIME temporal clause
15264        if let Some(ref system_time) = table.system_time {
15265            self.write_space();
15266            self.write(system_time);
15267        }
15268
15269        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15270        if let Some(ref version) = table.version {
15271            self.write_space();
15272            self.generate_version(version)?;
15273        }
15274
15275        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15276        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15277        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15278        let alias_post_tablesample = self.config.alias_post_tablesample;
15279
15280        if alias_post_tablesample {
15281            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15282            self.generate_table_sample_clause(table)?;
15283        }
15284
15285        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15286        // For SQLite, INDEXED BY hints come after the alias, so skip here
15287        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15288            && table.hints.iter().any(|h| {
15289                if let Expression::Identifier(id) = h {
15290                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15291                } else {
15292                    false
15293                }
15294            });
15295        if !table.hints.is_empty() && !is_sqlite_hint {
15296            for hint in &table.hints {
15297                self.write_space();
15298                self.generate_expression(hint)?;
15299            }
15300        }
15301
15302        if let Some(alias) = &table.alias {
15303            self.write_space();
15304            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15305            // Generic mode and most dialects always use AS for table aliases
15306            let always_use_as = self.config.dialect.is_none()
15307                || matches!(
15308                    self.config.dialect,
15309                    Some(DialectType::Generic)
15310                        | Some(DialectType::PostgreSQL)
15311                        | Some(DialectType::Redshift)
15312                        | Some(DialectType::Snowflake)
15313                        | Some(DialectType::BigQuery)
15314                        | Some(DialectType::Presto)
15315                        | Some(DialectType::Trino)
15316                        | Some(DialectType::TSQL)
15317                        | Some(DialectType::Fabric)
15318                        | Some(DialectType::MySQL)
15319                        | Some(DialectType::Spark)
15320                        | Some(DialectType::Hive)
15321                        | Some(DialectType::SQLite)
15322                        | Some(DialectType::Drill)
15323                );
15324            let is_stage_ref = table.name.name.starts_with('@');
15325            // Oracle never uses AS for table aliases
15326            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15327            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15328                self.write_keyword("AS");
15329                self.write_space();
15330            }
15331            self.generate_identifier(alias)?;
15332
15333            // Output column aliases if present: AS t(c1, c2)
15334            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15335            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15336                self.write("(");
15337                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15338                    if i > 0 {
15339                        self.write(", ");
15340                    }
15341                    self.generate_identifier(col_alias)?;
15342                }
15343                self.write(")");
15344            }
15345        }
15346
15347        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15348        if !alias_post_tablesample {
15349            self.generate_table_sample_clause(table)?;
15350        }
15351
15352        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15353        if is_sqlite_hint {
15354            for hint in &table.hints {
15355                self.write_space();
15356                self.generate_expression(hint)?;
15357            }
15358        }
15359
15360        // ClickHouse FINAL modifier
15361        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15362            self.write_space();
15363            self.write_keyword("FINAL");
15364        }
15365
15366        // Output trailing comments
15367        for comment in &table.trailing_comments {
15368            self.write_space();
15369            self.write_formatted_comment(comment);
15370        }
15371
15372        Ok(())
15373    }
15374
15375    /// Helper to output TABLESAMPLE clause for a table reference
15376    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15377        if let Some(ref ts) = table.table_sample {
15378            self.write_space();
15379            if ts.is_using_sample {
15380                self.write_keyword("USING SAMPLE");
15381            } else {
15382                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15383                self.write_keyword(self.config.tablesample_keywords);
15384            }
15385            self.generate_sample_body(ts)?;
15386            // Seed for table-level sample - use dialect's configured keyword
15387            if let Some(ref seed) = ts.seed {
15388                self.write_space();
15389                self.write_keyword(self.config.tablesample_seed_keyword);
15390                self.write(" (");
15391                self.generate_expression(seed)?;
15392                self.write(")");
15393            }
15394        }
15395        Ok(())
15396    }
15397
15398    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15399        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15400        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15401
15402        if sr.quoted {
15403            self.write("'");
15404        }
15405
15406        self.write(&sr.name);
15407        if let Some(path) = &sr.path {
15408            self.write(path);
15409        }
15410
15411        if sr.quoted {
15412            self.write("'");
15413        }
15414
15415        // Output FILE_FORMAT and PATTERN if present
15416        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15417        if has_options {
15418            self.write(" (");
15419            let mut first = true;
15420
15421            if let Some(file_format) = &sr.file_format {
15422                if !first {
15423                    self.write(", ");
15424                }
15425                self.write_keyword("FILE_FORMAT");
15426                self.write(" => ");
15427                self.generate_expression(file_format)?;
15428                first = false;
15429            }
15430
15431            if let Some(pattern) = &sr.pattern {
15432                if !first {
15433                    self.write(", ");
15434                }
15435                self.write_keyword("PATTERN");
15436                self.write(" => '");
15437                self.write(pattern);
15438                self.write("'");
15439            }
15440
15441            self.write(")");
15442        }
15443        Ok(())
15444    }
15445
15446    fn generate_star(&mut self, star: &Star) -> Result<()> {
15447        use crate::dialects::DialectType;
15448
15449        if let Some(table) = &star.table {
15450            self.generate_identifier(table)?;
15451            self.write(".");
15452        }
15453        self.write("*");
15454
15455        // Generate EXCLUDE/EXCEPT clause based on dialect
15456        if let Some(except) = &star.except {
15457            if !except.is_empty() {
15458                self.write_space();
15459                // Use dialect-appropriate keyword
15460                match self.config.dialect {
15461                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15462                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15463                        self.write_keyword("EXCLUDE")
15464                    }
15465                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15466                }
15467                self.write(" (");
15468                for (i, col) in except.iter().enumerate() {
15469                    if i > 0 {
15470                        self.write(", ");
15471                    }
15472                    self.generate_identifier(col)?;
15473                }
15474                self.write(")");
15475            }
15476        }
15477
15478        // Generate REPLACE clause
15479        if let Some(replace) = &star.replace {
15480            if !replace.is_empty() {
15481                self.write_space();
15482                self.write_keyword("REPLACE");
15483                self.write(" (");
15484                for (i, alias) in replace.iter().enumerate() {
15485                    if i > 0 {
15486                        self.write(", ");
15487                    }
15488                    self.generate_expression(&alias.this)?;
15489                    self.write_space();
15490                    self.write_keyword("AS");
15491                    self.write_space();
15492                    self.generate_identifier(&alias.alias)?;
15493                }
15494                self.write(")");
15495            }
15496        }
15497
15498        // Generate RENAME clause (Snowflake specific)
15499        if let Some(rename) = &star.rename {
15500            if !rename.is_empty() {
15501                self.write_space();
15502                self.write_keyword("RENAME");
15503                self.write(" (");
15504                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15505                    if i > 0 {
15506                        self.write(", ");
15507                    }
15508                    self.generate_identifier(old_name)?;
15509                    self.write_space();
15510                    self.write_keyword("AS");
15511                    self.write_space();
15512                    self.generate_identifier(new_name)?;
15513                }
15514                self.write(")");
15515            }
15516        }
15517
15518        // Output trailing comments
15519        for comment in &star.trailing_comments {
15520            self.write_space();
15521            self.write_formatted_comment(comment);
15522        }
15523
15524        Ok(())
15525    }
15526
15527    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15528    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15529        self.write("{");
15530        match expr {
15531            Expression::Star(star) => {
15532                // Generate the star (table.* or just * with optional EXCLUDE)
15533                self.generate_star(star)?;
15534            }
15535            Expression::ILike(ilike) => {
15536                // {* ILIKE 'pattern'} syntax
15537                self.generate_expression(&ilike.left)?;
15538                self.write_space();
15539                self.write_keyword("ILIKE");
15540                self.write_space();
15541                self.generate_expression(&ilike.right)?;
15542            }
15543            _ => {
15544                self.generate_expression(expr)?;
15545            }
15546        }
15547        self.write("}");
15548        Ok(())
15549    }
15550
15551    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15552        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15553        // to avoid duplication (comments are captured as both Column.trailing_comments
15554        // and Alias.pre_alias_comments during parsing)
15555        match &alias.this {
15556            Expression::Column(col) => {
15557                // Generate column without trailing comments - they're in pre_alias_comments
15558                if let Some(table) = &col.table {
15559                    self.generate_identifier(table)?;
15560                    self.write(".");
15561                }
15562                self.generate_identifier(&col.name)?;
15563            }
15564            _ => {
15565                self.generate_expression(&alias.this)?;
15566            }
15567        }
15568
15569        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15570        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15571        // keep pre-alias comments in their original position (between expression and AS).
15572        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15573            for comment in &alias.pre_alias_comments {
15574                self.write_space();
15575                self.write_formatted_comment(comment);
15576            }
15577        }
15578
15579        use crate::dialects::DialectType;
15580
15581        // Determine if we should skip AS keyword for table-valued function aliases
15582        // Oracle and some other dialects don't use AS for table aliases
15583        // Note: We specifically use TableFromRows here, NOT Function, because Function
15584        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15585        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15586        let is_table_source = matches!(
15587            &alias.this,
15588            Expression::JSONTable(_)
15589                | Expression::XMLTable(_)
15590                | Expression::TableFromRows(_)
15591                | Expression::Unnest(_)
15592                | Expression::MatchRecognize(_)
15593                | Expression::Select(_)
15594                | Expression::Subquery(_)
15595                | Expression::Paren(_)
15596        );
15597        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15598        let skip_as = is_table_source && dialect_skips_table_alias_as;
15599
15600        self.write_space();
15601        if !skip_as {
15602            self.write_keyword("AS");
15603            self.write_space();
15604        }
15605
15606        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15607        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15608
15609        // Check if we have column aliases only (no table alias name)
15610        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15611            // Generate AS (col1, col2, ...)
15612            self.write("(");
15613            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15614                if i > 0 {
15615                    self.write(", ");
15616                }
15617                self.generate_alias_identifier(col_alias)?;
15618            }
15619            self.write(")");
15620        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15621            // Generate AS alias(col1, col2, ...)
15622            self.generate_alias_identifier(&alias.alias)?;
15623            self.write("(");
15624            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15625                if i > 0 {
15626                    self.write(", ");
15627                }
15628                self.generate_alias_identifier(col_alias)?;
15629            }
15630            self.write(")");
15631        } else {
15632            // Simple alias (or BigQuery without column aliases)
15633            self.generate_alias_identifier(&alias.alias)?;
15634        }
15635
15636        // Output trailing comments (comments after the alias)
15637        for comment in &alias.trailing_comments {
15638            self.write_space();
15639            self.write_formatted_comment(comment);
15640        }
15641
15642        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15643        // moves pre-alias comments to after the alias. When there are trailing_comments,
15644        // the pre-alias comments were already lost (consumed as column trailing comments
15645        // that were then used as pre_alias_comments). We always emit them after alias.
15646        if alias.trailing_comments.is_empty() {
15647            for comment in &alias.pre_alias_comments {
15648                self.write_space();
15649                self.write_formatted_comment(comment);
15650            }
15651        }
15652
15653        Ok(())
15654    }
15655
15656    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15657        use crate::dialects::DialectType;
15658
15659        // SingleStore uses :> syntax
15660        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15661            self.generate_expression(&cast.this)?;
15662            self.write(" :> ");
15663            self.generate_data_type(&cast.to)?;
15664            return Ok(());
15665        }
15666
15667        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15668        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15669            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15670                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15671            if is_unknown_type {
15672                if let Some(format) = &cast.format {
15673                    self.write_keyword("CAST");
15674                    self.write("(");
15675                    self.generate_expression(&cast.this)?;
15676                    self.write_space();
15677                    self.write_keyword("AS");
15678                    self.write_space();
15679                    self.write_keyword("FORMAT");
15680                    self.write_space();
15681                    self.generate_expression(format)?;
15682                    self.write(")");
15683                    return Ok(());
15684                }
15685            }
15686        }
15687
15688        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15689        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15690        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15691            if let Some(format) = &cast.format {
15692                // Check if target type is DATE or TIMESTAMP
15693                let is_date = matches!(cast.to, DataType::Date);
15694                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15695
15696                if is_date || is_timestamp {
15697                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15698                    self.write_keyword(func_name);
15699                    self.write("(");
15700                    self.generate_expression(&cast.this)?;
15701                    self.write(", ");
15702
15703                    // Normalize format string for Oracle (HH -> HH12)
15704                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15705                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15706                        let normalized = self.normalize_oracle_format(fmt_str);
15707                        self.write("'");
15708                        self.write(&normalized);
15709                        self.write("'");
15710                    } else {
15711                        self.generate_expression(format)?;
15712                    }
15713
15714                    self.write(")");
15715                    return Ok(());
15716                }
15717            }
15718        }
15719
15720        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15721        // This preserves sqlglot's typed inline array literal output.
15722        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15723            if let Expression::Array(arr) = &cast.this {
15724                self.generate_data_type(&cast.to)?;
15725                // Output just the bracket content [values] without the ARRAY prefix
15726                self.write("[");
15727                for (i, expr) in arr.expressions.iter().enumerate() {
15728                    if i > 0 {
15729                        self.write(", ");
15730                    }
15731                    self.generate_expression(expr)?;
15732                }
15733                self.write("]");
15734                return Ok(());
15735            }
15736            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15737                self.generate_data_type(&cast.to)?;
15738                self.generate_expression(&cast.this)?;
15739                return Ok(());
15740            }
15741        }
15742
15743        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15744        // convert the inner Struct to ROW(values...) format
15745        if matches!(
15746            self.config.dialect,
15747            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15748        ) {
15749            if let Expression::Struct(ref s) = cast.this {
15750                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15751                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15752                    self.write_keyword("CAST");
15753                    self.write("(");
15754                    self.generate_struct_as_row(s)?;
15755                    self.write_space();
15756                    self.write_keyword("AS");
15757                    self.write_space();
15758                    self.generate_data_type(&cast.to)?;
15759                    self.write(")");
15760                    return Ok(());
15761                }
15762            }
15763        }
15764
15765        // Determine if we should use :: syntax based on dialect
15766        // PostgreSQL prefers :: for identity, most others prefer CAST()
15767        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15768
15769        if use_double_colon {
15770            // PostgreSQL :: syntax: expr::type
15771            self.generate_expression(&cast.this)?;
15772            self.write("::");
15773            self.generate_data_type(&cast.to)?;
15774        } else {
15775            // Standard CAST() syntax
15776            self.write_keyword("CAST");
15777            self.write("(");
15778            self.generate_expression(&cast.this)?;
15779            self.write_space();
15780            self.write_keyword("AS");
15781            self.write_space();
15782            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15783            // This matches Python sqlglot's CAST_MAPPING behavior
15784            if matches!(
15785                self.config.dialect,
15786                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15787            ) {
15788                match &cast.to {
15789                    DataType::Custom { ref name } => {
15790                        let upper = name.to_uppercase();
15791                        match upper.as_str() {
15792                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15793                            | "TINYBLOB" => {
15794                                self.write_keyword("CHAR");
15795                            }
15796                            _ => {
15797                                self.generate_data_type(&cast.to)?;
15798                            }
15799                        }
15800                    }
15801                    DataType::VarChar { length, .. } => {
15802                        // MySQL CAST: VARCHAR -> CHAR
15803                        self.write_keyword("CHAR");
15804                        if let Some(n) = length {
15805                            self.write(&format!("({})", n));
15806                        }
15807                    }
15808                    DataType::Text => {
15809                        // MySQL CAST: TEXT -> CHAR
15810                        self.write_keyword("CHAR");
15811                    }
15812                    DataType::Timestamp {
15813                        precision,
15814                        timezone: false,
15815                    } => {
15816                        // MySQL CAST: TIMESTAMP -> DATETIME
15817                        self.write_keyword("DATETIME");
15818                        if let Some(p) = precision {
15819                            self.write(&format!("({})", p));
15820                        }
15821                    }
15822                    _ => {
15823                        self.generate_data_type(&cast.to)?;
15824                    }
15825                }
15826            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15827                // Snowflake CAST: STRING -> VARCHAR
15828                match &cast.to {
15829                    DataType::String { length } => {
15830                        self.write_keyword("VARCHAR");
15831                        if let Some(n) = length {
15832                            self.write(&format!("({})", n));
15833                        }
15834                    }
15835                    _ => {
15836                        self.generate_data_type(&cast.to)?;
15837                    }
15838                }
15839            } else {
15840                self.generate_data_type(&cast.to)?;
15841            }
15842
15843            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15844            if let Some(default) = &cast.default {
15845                self.write_space();
15846                self.write_keyword("DEFAULT");
15847                self.write_space();
15848                self.generate_expression(default)?;
15849                self.write_space();
15850                self.write_keyword("ON");
15851                self.write_space();
15852                self.write_keyword("CONVERSION");
15853                self.write_space();
15854                self.write_keyword("ERROR");
15855            }
15856
15857            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15858            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15859            if let Some(format) = &cast.format {
15860                // Check if Oracle dialect - use comma syntax
15861                if matches!(
15862                    self.config.dialect,
15863                    Some(crate::dialects::DialectType::Oracle)
15864                ) {
15865                    self.write(", ");
15866                } else {
15867                    self.write_space();
15868                    self.write_keyword("FORMAT");
15869                    self.write_space();
15870                }
15871                self.generate_expression(format)?;
15872            }
15873
15874            self.write(")");
15875            // Output trailing comments
15876            for comment in &cast.trailing_comments {
15877                self.write_space();
15878                self.write_formatted_comment(comment);
15879            }
15880        }
15881        Ok(())
15882    }
15883
15884    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15885    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15886    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15887        self.write_keyword("ROW");
15888        self.write("(");
15889        for (i, (_, expr)) in s.fields.iter().enumerate() {
15890            if i > 0 {
15891                self.write(", ");
15892            }
15893            // Recursively convert inner Struct to ROW format
15894            if let Expression::Struct(ref inner_s) = expr {
15895                self.generate_struct_as_row(inner_s)?;
15896            } else {
15897                self.generate_expression(expr)?;
15898            }
15899        }
15900        self.write(")");
15901        Ok(())
15902    }
15903
15904    /// Normalize Oracle date/time format strings
15905    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15906    fn normalize_oracle_format(&self, format: &str) -> String {
15907        // Replace standalone HH with HH12 (but not HH12 or HH24)
15908        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15909        let mut result = String::new();
15910        let chars: Vec<char> = format.chars().collect();
15911        let mut i = 0;
15912
15913        while i < chars.len() {
15914            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15915                // Check what follows HH
15916                if i + 2 < chars.len() {
15917                    let next = chars[i + 2];
15918                    if next == '1' || next == '2' {
15919                        // This is HH12 or HH24, keep as is
15920                        result.push('H');
15921                        result.push('H');
15922                        i += 2;
15923                        continue;
15924                    }
15925                }
15926                // Standalone HH -> HH12
15927                result.push_str("HH12");
15928                i += 2;
15929            } else {
15930                result.push(chars[i]);
15931                i += 1;
15932            }
15933        }
15934
15935        result
15936    }
15937
15938    /// Check if the current dialect prefers :: cast syntax
15939    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15940    /// So we return false for all dialects to match Python sqlglot's behavior
15941    fn dialect_prefers_double_colon(&self) -> bool {
15942        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15943        // Even PostgreSQL outputs CAST() not ::
15944        false
15945    }
15946
15947    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15948    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15949        use crate::dialects::DialectType;
15950
15951        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15952        let use_percent_operator = matches!(
15953            self.config.dialect,
15954            Some(DialectType::Snowflake)
15955                | Some(DialectType::MySQL)
15956                | Some(DialectType::Presto)
15957                | Some(DialectType::Trino)
15958                | Some(DialectType::PostgreSQL)
15959                | Some(DialectType::DuckDB)
15960                | Some(DialectType::Hive)
15961                | Some(DialectType::Spark)
15962                | Some(DialectType::Databricks)
15963                | Some(DialectType::Athena)
15964        );
15965
15966        if use_percent_operator {
15967            // Wrap complex expressions in parens to preserve precedence
15968            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15969            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15970            if needs_paren(&f.this) {
15971                self.write("(");
15972                self.generate_expression(&f.this)?;
15973                self.write(")");
15974            } else {
15975                self.generate_expression(&f.this)?;
15976            }
15977            self.write(" % ");
15978            if needs_paren(&f.expression) {
15979                self.write("(");
15980                self.generate_expression(&f.expression)?;
15981                self.write(")");
15982            } else {
15983                self.generate_expression(&f.expression)?;
15984            }
15985            Ok(())
15986        } else {
15987            self.generate_binary_func("MOD", &f.this, &f.expression)
15988        }
15989    }
15990
15991    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15992    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15993        use crate::dialects::DialectType;
15994
15995        // Snowflake normalizes IFNULL to COALESCE
15996        let func_name = match self.config.dialect {
15997            Some(DialectType::Snowflake) => "COALESCE",
15998            _ => "IFNULL",
15999        };
16000
16001        self.generate_binary_func(func_name, &f.this, &f.expression)
16002    }
16003
16004    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16005    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16006        // Use original function name if preserved (for identity tests)
16007        if let Some(ref original_name) = f.original_name {
16008            return self.generate_binary_func(original_name, &f.this, &f.expression);
16009        }
16010
16011        // Otherwise, use dialect-specific function names
16012        use crate::dialects::DialectType;
16013        let func_name = match self.config.dialect {
16014            Some(DialectType::Snowflake)
16015            | Some(DialectType::ClickHouse)
16016            | Some(DialectType::PostgreSQL)
16017            | Some(DialectType::Presto)
16018            | Some(DialectType::Trino)
16019            | Some(DialectType::Athena)
16020            | Some(DialectType::DuckDB)
16021            | Some(DialectType::BigQuery)
16022            | Some(DialectType::Spark)
16023            | Some(DialectType::Databricks)
16024            | Some(DialectType::Hive) => "COALESCE",
16025            Some(DialectType::MySQL)
16026            | Some(DialectType::Doris)
16027            | Some(DialectType::StarRocks)
16028            | Some(DialectType::SingleStore)
16029            | Some(DialectType::TiDB) => "IFNULL",
16030            _ => "NVL",
16031        };
16032
16033        self.generate_binary_func(func_name, &f.this, &f.expression)
16034    }
16035
16036    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16037    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16038        use crate::dialects::DialectType;
16039
16040        // Snowflake normalizes STDDEV_SAMP to STDDEV
16041        let func_name = match self.config.dialect {
16042            Some(DialectType::Snowflake) => "STDDEV",
16043            _ => "STDDEV_SAMP",
16044        };
16045
16046        self.generate_agg_func(func_name, f)
16047    }
16048
16049    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16050        self.generate_expression(&coll.this)?;
16051        self.write_space();
16052        self.write_keyword("COLLATE");
16053        self.write_space();
16054        if coll.quoted {
16055            // Single-quoted string: COLLATE 'de_DE'
16056            self.write("'");
16057            self.write(&coll.collation);
16058            self.write("'");
16059        } else if coll.double_quoted {
16060            // Double-quoted identifier: COLLATE "de_DE"
16061            self.write("\"");
16062            self.write(&coll.collation);
16063            self.write("\"");
16064        } else {
16065            // Unquoted identifier: COLLATE de_DE
16066            self.write(&coll.collation);
16067        }
16068        Ok(())
16069    }
16070
16071    fn generate_case(&mut self, case: &Case) -> Result<()> {
16072        // In pretty mode, decide whether to expand based on total text width
16073        let multiline_case = if self.config.pretty {
16074            // Build the flat representation to check width
16075            let mut statements: Vec<String> = Vec::new();
16076            let operand_str = if let Some(operand) = &case.operand {
16077                let s = self.generate_to_string(operand)?;
16078                statements.push(format!("CASE {}", s));
16079                s
16080            } else {
16081                statements.push("CASE".to_string());
16082                String::new()
16083            };
16084            let _ = operand_str;
16085            for (condition, result) in &case.whens {
16086                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16087                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16088            }
16089            if let Some(else_) = &case.else_ {
16090                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16091            }
16092            statements.push("END".to_string());
16093            self.too_wide(&statements)
16094        } else {
16095            false
16096        };
16097
16098        self.write_keyword("CASE");
16099        if let Some(operand) = &case.operand {
16100            self.write_space();
16101            self.generate_expression(operand)?;
16102        }
16103        if multiline_case {
16104            self.indent_level += 1;
16105        }
16106        for (condition, result) in &case.whens {
16107            if multiline_case {
16108                self.write_newline();
16109                self.write_indent();
16110            } else {
16111                self.write_space();
16112            }
16113            self.write_keyword("WHEN");
16114            self.write_space();
16115            self.generate_expression(condition)?;
16116            if multiline_case {
16117                self.write_newline();
16118                self.write_indent();
16119            } else {
16120                self.write_space();
16121            }
16122            self.write_keyword("THEN");
16123            self.write_space();
16124            self.generate_expression(result)?;
16125        }
16126        if let Some(else_) = &case.else_ {
16127            if multiline_case {
16128                self.write_newline();
16129                self.write_indent();
16130            } else {
16131                self.write_space();
16132            }
16133            self.write_keyword("ELSE");
16134            self.write_space();
16135            self.generate_expression(else_)?;
16136        }
16137        if multiline_case {
16138            self.indent_level -= 1;
16139            self.write_newline();
16140            self.write_indent();
16141        } else {
16142            self.write_space();
16143        }
16144        self.write_keyword("END");
16145        // Emit any comments that were attached to the CASE keyword
16146        for comment in &case.comments {
16147            self.write(" ");
16148            self.write_formatted_comment(comment);
16149        }
16150        Ok(())
16151    }
16152
16153    fn generate_function(&mut self, func: &Function) -> Result<()> {
16154        // Normalize function name based on dialect settings
16155        let normalized_name = self.normalize_func_name(&func.name);
16156        let upper_name = func.name.to_uppercase();
16157
16158        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16159        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16160            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16161        {
16162            self.write("LIST_FILTER(");
16163            self.write("[");
16164            for (i, arg) in func.args.iter().enumerate() {
16165                if i > 0 {
16166                    self.write(", ");
16167                }
16168                self.generate_expression(arg)?;
16169            }
16170            self.write("], _u -> NOT _u IS NULL)");
16171            return Ok(());
16172        }
16173
16174        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16175        if upper_name == "STRUCT"
16176            && !matches!(
16177                self.config.dialect,
16178                Some(DialectType::BigQuery)
16179                    | Some(DialectType::Spark)
16180                    | Some(DialectType::Databricks)
16181                    | Some(DialectType::Hive)
16182                    | None
16183            )
16184        {
16185            return self.generate_struct_function_cross_dialect(func);
16186        }
16187
16188        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16189        // This is an internal marker function for ::? JSON path syntax
16190        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16191            self.generate_expression(&func.args[0])?;
16192            self.write("::?");
16193            // Extract the key from the string literal
16194            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16195                self.write(key);
16196            } else {
16197                self.generate_expression(&func.args[1])?;
16198            }
16199            return Ok(());
16200        }
16201
16202        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16203        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16204            self.generate_expression(&func.args[0])?;
16205            self.write(" # ");
16206            self.generate_expression(&func.args[1])?;
16207            return Ok(());
16208        }
16209
16210        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16211        if matches!(
16212            self.config.dialect,
16213            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16214        ) && upper_name == "TRY"
16215            && func.args.len() == 1
16216        {
16217            self.generate_expression(&func.args[0])?;
16218            return Ok(());
16219        }
16220
16221        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16222        if self.config.dialect == Some(DialectType::ClickHouse)
16223            && upper_name == "TOSTARTOFDAY"
16224            && func.args.len() == 1
16225        {
16226            self.write("dateTrunc('DAY', ");
16227            self.generate_expression(&func.args[0])?;
16228            self.write(")");
16229            return Ok(());
16230        }
16231
16232        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16233        if self.config.dialect == Some(DialectType::Redshift)
16234            && upper_name == "CONCAT"
16235            && func.args.len() >= 2
16236        {
16237            for (i, arg) in func.args.iter().enumerate() {
16238                if i > 0 {
16239                    self.write(" || ");
16240                }
16241                self.generate_expression(arg)?;
16242            }
16243            return Ok(());
16244        }
16245
16246        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16247        if self.config.dialect == Some(DialectType::Redshift)
16248            && upper_name == "CONCAT_WS"
16249            && func.args.len() >= 2
16250        {
16251            let sep = &func.args[0];
16252            for (i, arg) in func.args.iter().skip(1).enumerate() {
16253                if i > 0 {
16254                    self.write(" || ");
16255                    self.generate_expression(sep)?;
16256                    self.write(" || ");
16257                }
16258                self.generate_expression(arg)?;
16259            }
16260            return Ok(());
16261        }
16262
16263        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16264        // Unit should be unquoted uppercase identifier
16265        if self.config.dialect == Some(DialectType::Redshift)
16266            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16267            && func.args.len() == 3
16268        {
16269            self.write_keyword("DATEDIFF");
16270            self.write("(");
16271            // First arg is unit - normalize to unquoted uppercase
16272            self.write_redshift_date_part(&func.args[0]);
16273            self.write(", ");
16274            self.generate_expression(&func.args[1])?;
16275            self.write(", ");
16276            self.generate_expression(&func.args[2])?;
16277            self.write(")");
16278            return Ok(());
16279        }
16280
16281        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16282        // Unit should be unquoted uppercase identifier
16283        if self.config.dialect == Some(DialectType::Redshift)
16284            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16285            && func.args.len() == 3
16286        {
16287            self.write_keyword("DATEADD");
16288            self.write("(");
16289            // First arg is unit - normalize to unquoted uppercase
16290            self.write_redshift_date_part(&func.args[0]);
16291            self.write(", ");
16292            self.generate_expression(&func.args[1])?;
16293            self.write(", ");
16294            self.generate_expression(&func.args[2])?;
16295            self.write(")");
16296            return Ok(());
16297        }
16298
16299        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16300        if upper_name == "UUID_STRING"
16301            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16302        {
16303            let func_name = match self.config.dialect {
16304                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16305                Some(DialectType::BigQuery) => "GENERATE_UUID",
16306                _ => "UUID",
16307            };
16308            self.write_keyword(func_name);
16309            self.write("()");
16310            return Ok(());
16311        }
16312
16313        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16314        // Unit should be quoted uppercase string
16315        if self.config.dialect == Some(DialectType::Redshift)
16316            && upper_name == "DATE_TRUNC"
16317            && func.args.len() == 2
16318        {
16319            self.write_keyword("DATE_TRUNC");
16320            self.write("(");
16321            // First arg is unit - normalize to quoted uppercase
16322            self.write_redshift_date_part_quoted(&func.args[0]);
16323            self.write(", ");
16324            self.generate_expression(&func.args[1])?;
16325            self.write(")");
16326            return Ok(());
16327        }
16328
16329        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16330        if matches!(
16331            self.config.dialect,
16332            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16333        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16334            && func.args.len() == 2
16335        {
16336            self.write_keyword("DATEPART");
16337            self.write("(");
16338            self.generate_expression(&func.args[0])?;
16339            self.write(", ");
16340            self.generate_expression(&func.args[1])?;
16341            self.write(")");
16342            return Ok(());
16343        }
16344
16345        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16346        if matches!(
16347            self.config.dialect,
16348            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16349        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16350            && func.args.len() == 2
16351        {
16352            self.write_keyword("EXTRACT");
16353            self.write("(");
16354            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16355            match &func.args[0] {
16356                Expression::Literal(crate::expressions::Literal::String(s)) => {
16357                    self.write(&s.to_lowercase());
16358                }
16359                _ => self.generate_expression(&func.args[0])?,
16360            }
16361            self.write_space();
16362            self.write_keyword("FROM");
16363            self.write_space();
16364            self.generate_expression(&func.args[1])?;
16365            self.write(")");
16366            return Ok(());
16367        }
16368
16369        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16370        // Also DATE literals in Dremio should be CAST(...AS DATE)
16371        if self.config.dialect == Some(DialectType::Dremio)
16372            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16373            && func.args.len() == 2
16374        {
16375            self.write_keyword("EXTRACT");
16376            self.write("(");
16377            self.generate_expression(&func.args[0])?;
16378            self.write_space();
16379            self.write_keyword("FROM");
16380            self.write_space();
16381            // For Dremio, DATE literals should become CAST('value' AS DATE)
16382            self.generate_dremio_date_expression(&func.args[1])?;
16383            self.write(")");
16384            return Ok(());
16385        }
16386
16387        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16388        if self.config.dialect == Some(DialectType::Dremio)
16389            && upper_name == "CURRENT_DATE_UTC"
16390            && func.args.is_empty()
16391        {
16392            self.write_keyword("CURRENT_DATE_UTC");
16393            return Ok(());
16394        }
16395
16396        // Dremio: DATETYPE(year, month, day) transformation
16397        // - If all args are integer literals: DATE('YYYY-MM-DD')
16398        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16399        if self.config.dialect == Some(DialectType::Dremio)
16400            && upper_name == "DATETYPE"
16401            && func.args.len() == 3
16402        {
16403            // Helper function to extract integer from number literal
16404            fn get_int_literal(expr: &Expression) -> Option<i64> {
16405                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16406                    s.parse::<i64>().ok()
16407                } else {
16408                    None
16409                }
16410            }
16411
16412            // Check if all arguments are integer literals
16413            if let (Some(year), Some(month), Some(day)) = (
16414                get_int_literal(&func.args[0]),
16415                get_int_literal(&func.args[1]),
16416                get_int_literal(&func.args[2]),
16417            ) {
16418                // All are integer literals: DATE('YYYY-MM-DD')
16419                self.write_keyword("DATE");
16420                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16421                return Ok(());
16422            }
16423
16424            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16425            self.write_keyword("CAST");
16426            self.write("(");
16427            self.write_keyword("CONCAT");
16428            self.write("(");
16429            self.generate_expression(&func.args[0])?;
16430            self.write(", '-', ");
16431            self.generate_expression(&func.args[1])?;
16432            self.write(", '-', ");
16433            self.generate_expression(&func.args[2])?;
16434            self.write(")");
16435            self.write_space();
16436            self.write_keyword("AS");
16437            self.write_space();
16438            self.write_keyword("DATE");
16439            self.write(")");
16440            return Ok(());
16441        }
16442
16443        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16444        // when it's not an integer literal
16445        let is_presto_like = matches!(
16446            self.config.dialect,
16447            Some(DialectType::Presto) | Some(DialectType::Trino)
16448        );
16449        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16450            self.write_keyword("DATE_ADD");
16451            self.write("(");
16452            // First arg: unit (pass through as-is, e.g., 'DAY')
16453            self.generate_expression(&func.args[0])?;
16454            self.write(", ");
16455            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16456            let interval = &func.args[1];
16457            let needs_cast = !self.returns_integer_type(interval);
16458            if needs_cast {
16459                self.write_keyword("CAST");
16460                self.write("(");
16461            }
16462            self.generate_expression(interval)?;
16463            if needs_cast {
16464                self.write_space();
16465                self.write_keyword("AS");
16466                self.write_space();
16467                self.write_keyword("BIGINT");
16468                self.write(")");
16469            }
16470            self.write(", ");
16471            // Third arg: date
16472            self.generate_expression(&func.args[2])?;
16473            self.write(")");
16474            return Ok(());
16475        }
16476
16477        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16478        let use_brackets = func.use_bracket_syntax;
16479
16480        // Special case: functions WITH ORDINALITY need special output order
16481        // Input: FUNC(args) WITH ORDINALITY
16482        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16483        // Output must be: FUNC(args) WITH ORDINALITY
16484        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16485        let output_name = if has_ordinality {
16486            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16487            self.normalize_func_name(base_name)
16488        } else {
16489            normalized_name.clone()
16490        };
16491
16492        // For qualified names (schema.function or object.method), preserve original case
16493        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16494        if func.name.contains('.') && !has_ordinality {
16495            // Don't normalize qualified functions - preserve original case
16496            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16497            if func.quoted {
16498                self.write("`");
16499                self.write(&func.name);
16500                self.write("`");
16501            } else {
16502                self.write(&func.name);
16503            }
16504        } else {
16505            self.write(&output_name);
16506        }
16507
16508        // If no_parens is true and there are no args, output just the function name
16509        // Unless the target dialect requires parens for this function
16510        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16511            let needs_parens = match upper_name.as_str() {
16512                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16513                    self.config.dialect,
16514                    Some(DialectType::Snowflake)
16515                        | Some(DialectType::Spark)
16516                        | Some(DialectType::Databricks)
16517                        | Some(DialectType::Hive)
16518                ),
16519                _ => false,
16520            };
16521            !needs_parens
16522        };
16523        if force_parens {
16524            // Output trailing comments
16525            for comment in &func.trailing_comments {
16526                self.write_space();
16527                self.write_formatted_comment(comment);
16528            }
16529            return Ok(());
16530        }
16531
16532        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16533        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16534            self.write(" (");
16535        } else if use_brackets {
16536            self.write("[");
16537        } else {
16538            self.write("(");
16539        }
16540        if func.distinct {
16541            self.write_keyword("DISTINCT");
16542            self.write_space();
16543        }
16544
16545        // Check if arguments should be split onto multiple lines (pretty + too wide)
16546        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16547            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16548        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16549        let is_grouping_func =
16550            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16551        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16552            if is_grouping_func {
16553                true
16554            } else {
16555                // Pre-render arguments to check total width
16556                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16557                for arg in &func.args {
16558                    let mut temp_gen = Generator::with_config(self.config.clone());
16559                    temp_gen.config.pretty = false; // Don't recurse into pretty
16560                    temp_gen.generate_expression(arg)?;
16561                    expr_strings.push(temp_gen.output);
16562                }
16563                self.too_wide(&expr_strings)
16564            }
16565        } else {
16566            false
16567        };
16568
16569        if should_split {
16570            // Split onto multiple lines
16571            self.write_newline();
16572            self.indent_level += 1;
16573            for (i, arg) in func.args.iter().enumerate() {
16574                self.write_indent();
16575                self.generate_expression(arg)?;
16576                if i + 1 < func.args.len() {
16577                    self.write(",");
16578                }
16579                self.write_newline();
16580            }
16581            self.indent_level -= 1;
16582            self.write_indent();
16583        } else {
16584            // All on one line
16585            for (i, arg) in func.args.iter().enumerate() {
16586                if i > 0 {
16587                    self.write(", ");
16588                }
16589                self.generate_expression(arg)?;
16590            }
16591        }
16592
16593        if use_brackets {
16594            self.write("]");
16595        } else {
16596            self.write(")");
16597        }
16598        // Append WITH ORDINALITY after closing paren for table-valued functions
16599        if has_ordinality {
16600            self.write_space();
16601            self.write_keyword("WITH ORDINALITY");
16602        }
16603        // Output trailing comments
16604        for comment in &func.trailing_comments {
16605            self.write_space();
16606            self.write_formatted_comment(comment);
16607        }
16608        Ok(())
16609    }
16610
16611    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16612        // Normalize function name based on dialect settings
16613        let mut normalized_name = self.normalize_func_name(&func.name);
16614
16615        // Dialect-specific name mappings for aggregate functions
16616        let upper = normalized_name.to_uppercase();
16617        if upper == "MAX_BY" || upper == "MIN_BY" {
16618            let is_max = upper == "MAX_BY";
16619            match self.config.dialect {
16620                Some(DialectType::ClickHouse) => {
16621                    normalized_name = if is_max {
16622                        "argMax".to_string()
16623                    } else {
16624                        "argMin".to_string()
16625                    };
16626                }
16627                Some(DialectType::DuckDB) => {
16628                    normalized_name = if is_max {
16629                        "ARG_MAX".to_string()
16630                    } else {
16631                        "ARG_MIN".to_string()
16632                    };
16633                }
16634                _ => {}
16635            }
16636        }
16637        self.write(&normalized_name);
16638        self.write("(");
16639        if func.distinct {
16640            self.write_keyword("DISTINCT");
16641            self.write_space();
16642        }
16643
16644        // Check if we need to transform multi-arg COUNT DISTINCT
16645        // When dialect doesn't support multi_arg_distinct, transform:
16646        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16647        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16648        let needs_multi_arg_transform =
16649            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16650
16651        if needs_multi_arg_transform {
16652            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16653            self.write_keyword("CASE");
16654            for arg in &func.args {
16655                self.write_space();
16656                self.write_keyword("WHEN");
16657                self.write_space();
16658                self.generate_expression(arg)?;
16659                self.write_space();
16660                self.write_keyword("IS NULL THEN NULL");
16661            }
16662            self.write_space();
16663            self.write_keyword("ELSE");
16664            self.write(" (");
16665            for (i, arg) in func.args.iter().enumerate() {
16666                if i > 0 {
16667                    self.write(", ");
16668                }
16669                self.generate_expression(arg)?;
16670            }
16671            self.write(")");
16672            self.write_space();
16673            self.write_keyword("END");
16674        } else {
16675            for (i, arg) in func.args.iter().enumerate() {
16676                if i > 0 {
16677                    self.write(", ");
16678                }
16679                self.generate_expression(arg)?;
16680            }
16681        }
16682
16683        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16684        if self.config.ignore_nulls_in_func
16685            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16686        {
16687            if let Some(ignore) = func.ignore_nulls {
16688                self.write_space();
16689                if ignore {
16690                    self.write_keyword("IGNORE NULLS");
16691                } else {
16692                    self.write_keyword("RESPECT NULLS");
16693                }
16694            }
16695        }
16696
16697        // ORDER BY inside aggregate
16698        if !func.order_by.is_empty() {
16699            self.write_space();
16700            self.write_keyword("ORDER BY");
16701            self.write_space();
16702            for (i, ord) in func.order_by.iter().enumerate() {
16703                if i > 0 {
16704                    self.write(", ");
16705                }
16706                self.generate_ordered(ord)?;
16707            }
16708        }
16709
16710        // LIMIT inside aggregate
16711        if let Some(limit) = &func.limit {
16712            self.write_space();
16713            self.write_keyword("LIMIT");
16714            self.write_space();
16715            // Check if this is a Tuple representing LIMIT offset, count
16716            if let Expression::Tuple(t) = limit.as_ref() {
16717                if t.expressions.len() == 2 {
16718                    self.generate_expression(&t.expressions[0])?;
16719                    self.write(", ");
16720                    self.generate_expression(&t.expressions[1])?;
16721                } else {
16722                    self.generate_expression(limit)?;
16723                }
16724            } else {
16725                self.generate_expression(limit)?;
16726            }
16727        }
16728
16729        self.write(")");
16730
16731        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16732        if !self.config.ignore_nulls_in_func
16733            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16734        {
16735            if let Some(ignore) = func.ignore_nulls {
16736                self.write_space();
16737                if ignore {
16738                    self.write_keyword("IGNORE NULLS");
16739                } else {
16740                    self.write_keyword("RESPECT NULLS");
16741                }
16742            }
16743        }
16744
16745        if let Some(filter) = &func.filter {
16746            self.write_space();
16747            self.write_keyword("FILTER");
16748            self.write("(");
16749            self.write_keyword("WHERE");
16750            self.write_space();
16751            self.generate_expression(filter)?;
16752            self.write(")");
16753        }
16754
16755        Ok(())
16756    }
16757
16758    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16759        self.generate_expression(&wf.this)?;
16760
16761        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16762        if let Some(keep) = &wf.keep {
16763            self.write_space();
16764            self.write_keyword("KEEP");
16765            self.write(" (");
16766            self.write_keyword("DENSE_RANK");
16767            self.write_space();
16768            if keep.first {
16769                self.write_keyword("FIRST");
16770            } else {
16771                self.write_keyword("LAST");
16772            }
16773            self.write_space();
16774            self.write_keyword("ORDER BY");
16775            self.write_space();
16776            for (i, ord) in keep.order_by.iter().enumerate() {
16777                if i > 0 {
16778                    self.write(", ");
16779                }
16780                self.generate_ordered(ord)?;
16781            }
16782            self.write(")");
16783        }
16784
16785        // Check if there's any OVER clause content
16786        let has_over = !wf.over.partition_by.is_empty()
16787            || !wf.over.order_by.is_empty()
16788            || wf.over.frame.is_some()
16789            || wf.over.window_name.is_some();
16790
16791        // Only output OVER if there's actual window specification (not just KEEP alone)
16792        if has_over {
16793            self.write_space();
16794            self.write_keyword("OVER");
16795
16796            // Check if this is just a bare named window reference (no parens needed)
16797            let has_specs = !wf.over.partition_by.is_empty()
16798                || !wf.over.order_by.is_empty()
16799                || wf.over.frame.is_some();
16800
16801            if wf.over.window_name.is_some() && !has_specs {
16802                // OVER window_name (without parentheses)
16803                self.write_space();
16804                self.write(&wf.over.window_name.as_ref().unwrap().name);
16805            } else {
16806                // OVER (...) or OVER (window_name ...)
16807                self.write(" (");
16808                self.generate_over(&wf.over)?;
16809                self.write(")");
16810            }
16811        } else if wf.keep.is_none() {
16812            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16813            self.write_space();
16814            self.write_keyword("OVER");
16815            self.write(" ()");
16816        }
16817
16818        Ok(())
16819    }
16820
16821    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16822    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16823        self.generate_expression(&wg.this)?;
16824        self.write_space();
16825        self.write_keyword("WITHIN GROUP");
16826        self.write(" (");
16827        self.write_keyword("ORDER BY");
16828        self.write_space();
16829        for (i, ord) in wg.order_by.iter().enumerate() {
16830            if i > 0 {
16831                self.write(", ");
16832            }
16833            self.generate_ordered(ord)?;
16834        }
16835        self.write(")");
16836        Ok(())
16837    }
16838
16839    /// Generate the contents of an OVER clause (without parentheses)
16840    fn generate_over(&mut self, over: &Over) -> Result<()> {
16841        let mut has_content = false;
16842
16843        // Named window reference
16844        if let Some(name) = &over.window_name {
16845            self.write(&name.name);
16846            has_content = true;
16847        }
16848
16849        // PARTITION BY
16850        if !over.partition_by.is_empty() {
16851            if has_content {
16852                self.write_space();
16853            }
16854            self.write_keyword("PARTITION BY");
16855            self.write_space();
16856            for (i, expr) in over.partition_by.iter().enumerate() {
16857                if i > 0 {
16858                    self.write(", ");
16859                }
16860                self.generate_expression(expr)?;
16861            }
16862            has_content = true;
16863        }
16864
16865        // ORDER BY
16866        if !over.order_by.is_empty() {
16867            if has_content {
16868                self.write_space();
16869            }
16870            self.write_keyword("ORDER BY");
16871            self.write_space();
16872            for (i, ordered) in over.order_by.iter().enumerate() {
16873                if i > 0 {
16874                    self.write(", ");
16875                }
16876                self.generate_ordered(ordered)?;
16877            }
16878            has_content = true;
16879        }
16880
16881        // Window frame
16882        if let Some(frame) = &over.frame {
16883            if has_content {
16884                self.write_space();
16885            }
16886            self.generate_window_frame(frame)?;
16887        }
16888
16889        Ok(())
16890    }
16891
16892    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16893        // Exasol uses lowercase for frame kind (rows/range/groups)
16894        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16895
16896        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16897        if !lowercase_frame {
16898            if let Some(kind_text) = &frame.kind_text {
16899                self.write(kind_text);
16900            } else {
16901                match frame.kind {
16902                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16903                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16904                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16905                }
16906            }
16907        } else {
16908            match frame.kind {
16909                WindowFrameKind::Rows => self.write("rows"),
16910                WindowFrameKind::Range => self.write("range"),
16911                WindowFrameKind::Groups => self.write("groups"),
16912            }
16913        }
16914
16915        // Use BETWEEN format only when there's an explicit end bound,
16916        // or when normalize_window_frame_between is enabled and the start is a directional bound
16917        self.write_space();
16918        let should_normalize = self.config.normalize_window_frame_between
16919            && frame.end.is_none()
16920            && matches!(
16921                frame.start,
16922                WindowFrameBound::Preceding(_)
16923                    | WindowFrameBound::Following(_)
16924                    | WindowFrameBound::UnboundedPreceding
16925                    | WindowFrameBound::UnboundedFollowing
16926            );
16927
16928        if let Some(end) = &frame.end {
16929            // BETWEEN format: RANGE BETWEEN start AND end
16930            self.write_keyword("BETWEEN");
16931            self.write_space();
16932            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16933            self.write_space();
16934            self.write_keyword("AND");
16935            self.write_space();
16936            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16937        } else if should_normalize {
16938            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16939            self.write_keyword("BETWEEN");
16940            self.write_space();
16941            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16942            self.write_space();
16943            self.write_keyword("AND");
16944            self.write_space();
16945            self.write_keyword("CURRENT ROW");
16946        } else {
16947            // Single bound format: RANGE CURRENT ROW
16948            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16949        }
16950
16951        // EXCLUDE clause
16952        if let Some(exclude) = &frame.exclude {
16953            self.write_space();
16954            self.write_keyword("EXCLUDE");
16955            self.write_space();
16956            match exclude {
16957                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16958                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16959                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16960                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16961            }
16962        }
16963
16964        Ok(())
16965    }
16966
16967    fn generate_window_frame_bound(
16968        &mut self,
16969        bound: &WindowFrameBound,
16970        side_text: Option<&str>,
16971    ) -> Result<()> {
16972        // Exasol uses lowercase for preceding/following
16973        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16974
16975        match bound {
16976            WindowFrameBound::CurrentRow => {
16977                self.write_keyword("CURRENT ROW");
16978            }
16979            WindowFrameBound::UnboundedPreceding => {
16980                self.write_keyword("UNBOUNDED");
16981                self.write_space();
16982                if lowercase_frame {
16983                    self.write("preceding");
16984                } else if let Some(text) = side_text {
16985                    self.write(text);
16986                } else {
16987                    self.write_keyword("PRECEDING");
16988                }
16989            }
16990            WindowFrameBound::UnboundedFollowing => {
16991                self.write_keyword("UNBOUNDED");
16992                self.write_space();
16993                if lowercase_frame {
16994                    self.write("following");
16995                } else if let Some(text) = side_text {
16996                    self.write(text);
16997                } else {
16998                    self.write_keyword("FOLLOWING");
16999                }
17000            }
17001            WindowFrameBound::Preceding(expr) => {
17002                self.generate_expression(expr)?;
17003                self.write_space();
17004                if lowercase_frame {
17005                    self.write("preceding");
17006                } else if let Some(text) = side_text {
17007                    self.write(text);
17008                } else {
17009                    self.write_keyword("PRECEDING");
17010                }
17011            }
17012            WindowFrameBound::Following(expr) => {
17013                self.generate_expression(expr)?;
17014                self.write_space();
17015                if lowercase_frame {
17016                    self.write("following");
17017                } else if let Some(text) = side_text {
17018                    self.write(text);
17019                } else {
17020                    self.write_keyword("FOLLOWING");
17021                }
17022            }
17023            WindowFrameBound::BarePreceding => {
17024                if lowercase_frame {
17025                    self.write("preceding");
17026                } else if let Some(text) = side_text {
17027                    self.write(text);
17028                } else {
17029                    self.write_keyword("PRECEDING");
17030                }
17031            }
17032            WindowFrameBound::BareFollowing => {
17033                if lowercase_frame {
17034                    self.write("following");
17035                } else if let Some(text) = side_text {
17036                    self.write(text);
17037                } else {
17038                    self.write_keyword("FOLLOWING");
17039                }
17040            }
17041            WindowFrameBound::Value(expr) => {
17042                // Bare numeric bound without PRECEDING/FOLLOWING
17043                self.generate_expression(expr)?;
17044            }
17045        }
17046        Ok(())
17047    }
17048
17049    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
17050        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
17051        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
17052        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
17053            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
17054            && !matches!(&interval.this, Some(Expression::Literal(_)));
17055
17056        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
17057        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
17058        if self.config.single_string_interval {
17059            if let (
17060                Some(Expression::Literal(Literal::String(ref val))),
17061                Some(IntervalUnitSpec::Simple {
17062                    ref unit,
17063                    ref use_plural,
17064                }),
17065            ) = (&interval.this, &interval.unit)
17066            {
17067                self.write_keyword("INTERVAL");
17068                self.write_space();
17069                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17070                let unit_str = self.interval_unit_str(unit, effective_plural);
17071                self.write("'");
17072                self.write(val);
17073                self.write(" ");
17074                self.write(&unit_str);
17075                self.write("'");
17076                return Ok(());
17077            }
17078        }
17079
17080        if !skip_interval_keyword {
17081            self.write_keyword("INTERVAL");
17082        }
17083
17084        // Generate value if present
17085        if let Some(ref value) = interval.this {
17086            if !skip_interval_keyword {
17087                self.write_space();
17088            }
17089            // If the value is a complex expression (not a literal/column/function call)
17090            // and there's a unit, wrap it in parentheses
17091            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17092            let needs_parens = interval.unit.is_some()
17093                && matches!(
17094                    value,
17095                    Expression::Add(_)
17096                        | Expression::Sub(_)
17097                        | Expression::Mul(_)
17098                        | Expression::Div(_)
17099                        | Expression::Mod(_)
17100                        | Expression::BitwiseAnd(_)
17101                        | Expression::BitwiseOr(_)
17102                        | Expression::BitwiseXor(_)
17103                );
17104            if needs_parens {
17105                self.write("(");
17106            }
17107            self.generate_expression(value)?;
17108            if needs_parens {
17109                self.write(")");
17110            }
17111        }
17112
17113        // Generate unit if present
17114        if let Some(ref unit_spec) = interval.unit {
17115            self.write_space();
17116            self.write_interval_unit_spec(unit_spec)?;
17117        }
17118
17119        Ok(())
17120    }
17121
17122    /// Return the string representation of an interval unit
17123    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17124        match (unit, use_plural) {
17125            (IntervalUnit::Year, false) => "YEAR",
17126            (IntervalUnit::Year, true) => "YEARS",
17127            (IntervalUnit::Quarter, false) => "QUARTER",
17128            (IntervalUnit::Quarter, true) => "QUARTERS",
17129            (IntervalUnit::Month, false) => "MONTH",
17130            (IntervalUnit::Month, true) => "MONTHS",
17131            (IntervalUnit::Week, false) => "WEEK",
17132            (IntervalUnit::Week, true) => "WEEKS",
17133            (IntervalUnit::Day, false) => "DAY",
17134            (IntervalUnit::Day, true) => "DAYS",
17135            (IntervalUnit::Hour, false) => "HOUR",
17136            (IntervalUnit::Hour, true) => "HOURS",
17137            (IntervalUnit::Minute, false) => "MINUTE",
17138            (IntervalUnit::Minute, true) => "MINUTES",
17139            (IntervalUnit::Second, false) => "SECOND",
17140            (IntervalUnit::Second, true) => "SECONDS",
17141            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17142            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17143            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17144            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17145            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17146            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17147        }
17148    }
17149
17150    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17151        match unit_spec {
17152            IntervalUnitSpec::Simple { unit, use_plural } => {
17153                // If dialect doesn't allow plural forms, force singular
17154                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17155                self.write_simple_interval_unit(unit, effective_plural);
17156            }
17157            IntervalUnitSpec::Span(span) => {
17158                self.write_simple_interval_unit(&span.this, false);
17159                self.write_space();
17160                self.write_keyword("TO");
17161                self.write_space();
17162                self.write_simple_interval_unit(&span.expression, false);
17163            }
17164            IntervalUnitSpec::ExprSpan(span) => {
17165                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17166                self.generate_expression(&span.this)?;
17167                self.write_space();
17168                self.write_keyword("TO");
17169                self.write_space();
17170                self.generate_expression(&span.expression)?;
17171            }
17172            IntervalUnitSpec::Expr(expr) => {
17173                self.generate_expression(expr)?;
17174            }
17175        }
17176        Ok(())
17177    }
17178
17179    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17180        // Output interval unit, respecting plural preference
17181        match (unit, use_plural) {
17182            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17183            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17184            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17185            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17186            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17187            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17188            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17189            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17190            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17191            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17192            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17193            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17194            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17195            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17196            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17197            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17198            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17199            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17200            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17201            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17202            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17203            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17204        }
17205    }
17206
17207    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17208    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17209    fn write_redshift_date_part(&mut self, expr: &Expression) {
17210        let part_str = self.extract_date_part_string(expr);
17211        if let Some(part) = part_str {
17212            let normalized = self.normalize_date_part(&part);
17213            self.write_keyword(&normalized);
17214        } else {
17215            // If we can't extract a date part string, fall back to generating the expression
17216            let _ = self.generate_expression(expr);
17217        }
17218    }
17219
17220    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17221    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17222    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17223        let part_str = self.extract_date_part_string(expr);
17224        if let Some(part) = part_str {
17225            let normalized = self.normalize_date_part(&part);
17226            self.write("'");
17227            self.write(&normalized);
17228            self.write("'");
17229        } else {
17230            // If we can't extract a date part string, fall back to generating the expression
17231            let _ = self.generate_expression(expr);
17232        }
17233    }
17234
17235    /// Extract date part string from expression (handles string literals and identifiers)
17236    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17237        match expr {
17238            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17239            Expression::Identifier(id) => Some(id.name.clone()),
17240            Expression::Column(col) if col.table.is_none() => {
17241                // Simple column reference without table prefix, treat as identifier
17242                Some(col.name.name.clone())
17243            }
17244            _ => None,
17245        }
17246    }
17247
17248    /// Normalize date part to uppercase singular form
17249    /// days -> DAY, months -> MONTH, etc.
17250    fn normalize_date_part(&self, part: &str) -> String {
17251        let lower = part.to_lowercase();
17252        match lower.as_str() {
17253            "day" | "days" | "d" => "DAY".to_string(),
17254            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17255            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17256            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17257            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17258            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17259            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17260            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17261            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17262            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17263            _ => part.to_uppercase(),
17264        }
17265    }
17266
17267    fn write_datetime_field(&mut self, field: &DateTimeField) {
17268        match field {
17269            DateTimeField::Year => self.write_keyword("YEAR"),
17270            DateTimeField::Month => self.write_keyword("MONTH"),
17271            DateTimeField::Day => self.write_keyword("DAY"),
17272            DateTimeField::Hour => self.write_keyword("HOUR"),
17273            DateTimeField::Minute => self.write_keyword("MINUTE"),
17274            DateTimeField::Second => self.write_keyword("SECOND"),
17275            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17276            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17277            DateTimeField::DayOfWeek => {
17278                let name = match self.config.dialect {
17279                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17280                    _ => "DOW",
17281                };
17282                self.write_keyword(name);
17283            }
17284            DateTimeField::DayOfYear => {
17285                let name = match self.config.dialect {
17286                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17287                    _ => "DOY",
17288                };
17289                self.write_keyword(name);
17290            }
17291            DateTimeField::Week => self.write_keyword("WEEK"),
17292            DateTimeField::WeekWithModifier(modifier) => {
17293                self.write_keyword("WEEK");
17294                self.write("(");
17295                self.write(modifier);
17296                self.write(")");
17297            }
17298            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17299            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17300            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17301            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17302            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17303            DateTimeField::Date => self.write_keyword("DATE"),
17304            DateTimeField::Time => self.write_keyword("TIME"),
17305            DateTimeField::Custom(name) => self.write(name),
17306        }
17307    }
17308
17309    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17310    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17311        match field {
17312            DateTimeField::Year => self.write("year"),
17313            DateTimeField::Month => self.write("month"),
17314            DateTimeField::Day => self.write("day"),
17315            DateTimeField::Hour => self.write("hour"),
17316            DateTimeField::Minute => self.write("minute"),
17317            DateTimeField::Second => self.write("second"),
17318            DateTimeField::Millisecond => self.write("millisecond"),
17319            DateTimeField::Microsecond => self.write("microsecond"),
17320            DateTimeField::DayOfWeek => self.write("dow"),
17321            DateTimeField::DayOfYear => self.write("doy"),
17322            DateTimeField::Week => self.write("week"),
17323            DateTimeField::WeekWithModifier(modifier) => {
17324                self.write("week(");
17325                self.write(modifier);
17326                self.write(")");
17327            }
17328            DateTimeField::Quarter => self.write("quarter"),
17329            DateTimeField::Epoch => self.write("epoch"),
17330            DateTimeField::Timezone => self.write("timezone"),
17331            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17332            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17333            DateTimeField::Date => self.write("date"),
17334            DateTimeField::Time => self.write("time"),
17335            DateTimeField::Custom(name) => self.write(name),
17336        }
17337    }
17338
17339    // Helper function generators
17340
17341    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17342        self.write_keyword(name);
17343        self.write("(");
17344        self.generate_expression(arg)?;
17345        self.write(")");
17346        Ok(())
17347    }
17348
17349    /// Generate a unary function, using the original name if available for round-trip preservation
17350    fn generate_unary_func(
17351        &mut self,
17352        default_name: &str,
17353        f: &crate::expressions::UnaryFunc,
17354    ) -> Result<()> {
17355        let name = f.original_name.as_deref().unwrap_or(default_name);
17356        self.write_keyword(name);
17357        self.write("(");
17358        self.generate_expression(&f.this)?;
17359        self.write(")");
17360        Ok(())
17361    }
17362
17363    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17364    fn generate_sqrt_cbrt(
17365        &mut self,
17366        f: &crate::expressions::UnaryFunc,
17367        func_name: &str,
17368        _op: &str,
17369    ) -> Result<()> {
17370        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17371        // Always use function syntax for consistency
17372        self.write_keyword(func_name);
17373        self.write("(");
17374        self.generate_expression(&f.this)?;
17375        self.write(")");
17376        Ok(())
17377    }
17378
17379    fn generate_binary_func(
17380        &mut self,
17381        name: &str,
17382        arg1: &Expression,
17383        arg2: &Expression,
17384    ) -> Result<()> {
17385        self.write_keyword(name);
17386        self.write("(");
17387        self.generate_expression(arg1)?;
17388        self.write(", ");
17389        self.generate_expression(arg2)?;
17390        self.write(")");
17391        Ok(())
17392    }
17393
17394    /// Generate CHAR/CHR function with optional USING charset
17395    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17396    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17397    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17398        // Use stored name if available, otherwise default to CHAR
17399        let func_name = f.name.as_deref().unwrap_or("CHAR");
17400        self.write_keyword(func_name);
17401        self.write("(");
17402        for (i, arg) in f.args.iter().enumerate() {
17403            if i > 0 {
17404                self.write(", ");
17405            }
17406            self.generate_expression(arg)?;
17407        }
17408        if let Some(ref charset) = f.charset {
17409            self.write(" ");
17410            self.write_keyword("USING");
17411            self.write(" ");
17412            self.write(charset);
17413        }
17414        self.write(")");
17415        Ok(())
17416    }
17417
17418    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17419        use crate::dialects::DialectType;
17420
17421        match self.config.dialect {
17422            Some(DialectType::Teradata) => {
17423                // Teradata uses ** operator for exponentiation
17424                self.generate_expression(&f.this)?;
17425                self.write(" ** ");
17426                self.generate_expression(&f.expression)?;
17427                Ok(())
17428            }
17429            _ => {
17430                // Other dialects use POWER function
17431                self.generate_binary_func("POWER", &f.this, &f.expression)
17432            }
17433        }
17434    }
17435
17436    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17437        self.write_func_name(name);
17438        self.write("(");
17439        for (i, arg) in args.iter().enumerate() {
17440            if i > 0 {
17441                self.write(", ");
17442            }
17443            self.generate_expression(arg)?;
17444        }
17445        self.write(")");
17446        Ok(())
17447    }
17448
17449    // String function generators
17450
17451    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17452        self.write_keyword("CONCAT_WS");
17453        self.write("(");
17454        self.generate_expression(&f.separator)?;
17455        for expr in &f.expressions {
17456            self.write(", ");
17457            self.generate_expression(expr)?;
17458        }
17459        self.write(")");
17460        Ok(())
17461    }
17462
17463    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17464        if let Expression::Concat(op) = expr {
17465            Self::collect_concat_operands(&op.left, out);
17466            Self::collect_concat_operands(&op.right, out);
17467        } else {
17468            out.push(expr);
17469        }
17470    }
17471
17472    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
17473        let mut operands = Vec::new();
17474        Self::collect_concat_operands(&op.left, &mut operands);
17475        Self::collect_concat_operands(&op.right, &mut operands);
17476
17477        self.write_keyword("CONCAT");
17478        self.write("(");
17479        for (i, operand) in operands.iter().enumerate() {
17480            if i > 0 {
17481                self.write(", ");
17482            }
17483            self.generate_expression(operand)?;
17484        }
17485        self.write(")");
17486        Ok(())
17487    }
17488
17489    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
17490        if let Expression::DPipe(dpipe) = expr {
17491            Self::collect_dpipe_operands(&dpipe.this, out);
17492            Self::collect_dpipe_operands(&dpipe.expression, out);
17493        } else {
17494            out.push(expr);
17495        }
17496    }
17497
17498    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
17499        let mut operands = Vec::new();
17500        Self::collect_dpipe_operands(&e.this, &mut operands);
17501        Self::collect_dpipe_operands(&e.expression, &mut operands);
17502
17503        self.write_keyword("CONCAT");
17504        self.write("(");
17505        for (i, operand) in operands.iter().enumerate() {
17506            if i > 0 {
17507                self.write(", ");
17508            }
17509            self.generate_expression(operand)?;
17510        }
17511        self.write(")");
17512        Ok(())
17513    }
17514
17515    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17516        // Oracle uses SUBSTR; most others use SUBSTRING
17517        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17518        if is_oracle {
17519            self.write_keyword("SUBSTR");
17520        } else {
17521            self.write_keyword("SUBSTRING");
17522        }
17523        self.write("(");
17524        self.generate_expression(&f.this)?;
17525        // PostgreSQL always uses FROM/FOR syntax
17526        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17527        // Spark/Hive use comma syntax, not FROM/FOR syntax
17528        let use_comma_syntax = matches!(
17529            self.config.dialect,
17530            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17531        );
17532        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17533            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17534            self.write_space();
17535            self.write_keyword("FROM");
17536            self.write_space();
17537            self.generate_expression(&f.start)?;
17538            if let Some(length) = &f.length {
17539                self.write_space();
17540                self.write_keyword("FOR");
17541                self.write_space();
17542                self.generate_expression(length)?;
17543            }
17544        } else {
17545            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17546            self.write(", ");
17547            self.generate_expression(&f.start)?;
17548            if let Some(length) = &f.length {
17549                self.write(", ");
17550                self.generate_expression(length)?;
17551            }
17552        }
17553        self.write(")");
17554        Ok(())
17555    }
17556
17557    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17558        self.write_keyword("OVERLAY");
17559        self.write("(");
17560        self.generate_expression(&f.this)?;
17561        self.write_space();
17562        self.write_keyword("PLACING");
17563        self.write_space();
17564        self.generate_expression(&f.replacement)?;
17565        self.write_space();
17566        self.write_keyword("FROM");
17567        self.write_space();
17568        self.generate_expression(&f.from)?;
17569        if let Some(length) = &f.length {
17570            self.write_space();
17571            self.write_keyword("FOR");
17572            self.write_space();
17573            self.generate_expression(length)?;
17574        }
17575        self.write(")");
17576        Ok(())
17577    }
17578
17579    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17580        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17581        // when no characters are specified (PostgreSQL style)
17582        if f.position_explicit && f.characters.is_none() {
17583            match f.position {
17584                TrimPosition::Leading => {
17585                    self.write_keyword("LTRIM");
17586                    self.write("(");
17587                    self.generate_expression(&f.this)?;
17588                    self.write(")");
17589                    return Ok(());
17590                }
17591                TrimPosition::Trailing => {
17592                    self.write_keyword("RTRIM");
17593                    self.write("(");
17594                    self.generate_expression(&f.this)?;
17595                    self.write(")");
17596                    return Ok(());
17597                }
17598                TrimPosition::Both => {
17599                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17600                    // Fall through to standard TRIM handling
17601                }
17602            }
17603        }
17604
17605        self.write_keyword("TRIM");
17606        self.write("(");
17607        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17608        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17609        let force_standard = f.characters.is_some()
17610            && !f.sql_standard_syntax
17611            && matches!(
17612                self.config.dialect,
17613                Some(DialectType::Hive)
17614                    | Some(DialectType::Spark)
17615                    | Some(DialectType::Databricks)
17616                    | Some(DialectType::ClickHouse)
17617            );
17618        let use_standard = (f.sql_standard_syntax || force_standard)
17619            && !(f.position_explicit
17620                && f.characters.is_none()
17621                && matches!(f.position, TrimPosition::Both));
17622        if use_standard {
17623            // SQL standard syntax: TRIM(BOTH chars FROM str)
17624            // Only output position if it was explicitly specified
17625            if f.position_explicit {
17626                match f.position {
17627                    TrimPosition::Both => self.write_keyword("BOTH"),
17628                    TrimPosition::Leading => self.write_keyword("LEADING"),
17629                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17630                }
17631                self.write_space();
17632            }
17633            if let Some(chars) = &f.characters {
17634                self.generate_expression(chars)?;
17635                self.write_space();
17636            }
17637            self.write_keyword("FROM");
17638            self.write_space();
17639            self.generate_expression(&f.this)?;
17640        } else {
17641            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17642            self.generate_expression(&f.this)?;
17643            if let Some(chars) = &f.characters {
17644                self.write(", ");
17645                self.generate_expression(chars)?;
17646            }
17647        }
17648        self.write(")");
17649        Ok(())
17650    }
17651
17652    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17653        self.write_keyword("REPLACE");
17654        self.write("(");
17655        self.generate_expression(&f.this)?;
17656        self.write(", ");
17657        self.generate_expression(&f.old)?;
17658        self.write(", ");
17659        self.generate_expression(&f.new)?;
17660        self.write(")");
17661        Ok(())
17662    }
17663
17664    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17665        self.write_keyword(name);
17666        self.write("(");
17667        self.generate_expression(&f.this)?;
17668        self.write(", ");
17669        self.generate_expression(&f.length)?;
17670        self.write(")");
17671        Ok(())
17672    }
17673
17674    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17675        self.write_keyword("REPEAT");
17676        self.write("(");
17677        self.generate_expression(&f.this)?;
17678        self.write(", ");
17679        self.generate_expression(&f.times)?;
17680        self.write(")");
17681        Ok(())
17682    }
17683
17684    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17685        self.write_keyword(name);
17686        self.write("(");
17687        self.generate_expression(&f.this)?;
17688        self.write(", ");
17689        self.generate_expression(&f.length)?;
17690        if let Some(fill) = &f.fill {
17691            self.write(", ");
17692            self.generate_expression(fill)?;
17693        }
17694        self.write(")");
17695        Ok(())
17696    }
17697
17698    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17699        self.write_keyword("SPLIT");
17700        self.write("(");
17701        self.generate_expression(&f.this)?;
17702        self.write(", ");
17703        self.generate_expression(&f.delimiter)?;
17704        self.write(")");
17705        Ok(())
17706    }
17707
17708    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17709        use crate::dialects::DialectType;
17710        // PostgreSQL uses ~ operator for regex matching
17711        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17712            self.generate_expression(&f.this)?;
17713            self.write(" ~ ");
17714            self.generate_expression(&f.pattern)?;
17715        } else if matches!(
17716            self.config.dialect,
17717            Some(DialectType::SingleStore)
17718                | Some(DialectType::Spark)
17719                | Some(DialectType::Hive)
17720                | Some(DialectType::Databricks)
17721        ) && f.flags.is_none()
17722        {
17723            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17724            self.generate_expression(&f.this)?;
17725            self.write_keyword(" RLIKE ");
17726            self.generate_expression(&f.pattern)?;
17727        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17728            // StarRocks uses REGEXP function syntax
17729            self.write_keyword("REGEXP");
17730            self.write("(");
17731            self.generate_expression(&f.this)?;
17732            self.write(", ");
17733            self.generate_expression(&f.pattern)?;
17734            if let Some(flags) = &f.flags {
17735                self.write(", ");
17736                self.generate_expression(flags)?;
17737            }
17738            self.write(")");
17739        } else {
17740            self.write_keyword("REGEXP_LIKE");
17741            self.write("(");
17742            self.generate_expression(&f.this)?;
17743            self.write(", ");
17744            self.generate_expression(&f.pattern)?;
17745            if let Some(flags) = &f.flags {
17746                self.write(", ");
17747                self.generate_expression(flags)?;
17748            }
17749            self.write(")");
17750        }
17751        Ok(())
17752    }
17753
17754    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17755        self.write_keyword("REGEXP_REPLACE");
17756        self.write("(");
17757        self.generate_expression(&f.this)?;
17758        self.write(", ");
17759        self.generate_expression(&f.pattern)?;
17760        self.write(", ");
17761        self.generate_expression(&f.replacement)?;
17762        if let Some(flags) = &f.flags {
17763            self.write(", ");
17764            self.generate_expression(flags)?;
17765        }
17766        self.write(")");
17767        Ok(())
17768    }
17769
17770    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17771        self.write_keyword("REGEXP_EXTRACT");
17772        self.write("(");
17773        self.generate_expression(&f.this)?;
17774        self.write(", ");
17775        self.generate_expression(&f.pattern)?;
17776        if let Some(group) = &f.group {
17777            self.write(", ");
17778            self.generate_expression(group)?;
17779        }
17780        self.write(")");
17781        Ok(())
17782    }
17783
17784    // Math function generators
17785
17786    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17787        self.write_keyword("ROUND");
17788        self.write("(");
17789        self.generate_expression(&f.this)?;
17790        if let Some(decimals) = &f.decimals {
17791            self.write(", ");
17792            self.generate_expression(decimals)?;
17793        }
17794        self.write(")");
17795        Ok(())
17796    }
17797
17798    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17799        self.write_keyword("FLOOR");
17800        self.write("(");
17801        self.generate_expression(&f.this)?;
17802        // Handle Druid-style FLOOR(time TO unit) syntax
17803        if let Some(to) = &f.to {
17804            self.write(" ");
17805            self.write_keyword("TO");
17806            self.write(" ");
17807            self.generate_expression(to)?;
17808        } else if let Some(scale) = &f.scale {
17809            self.write(", ");
17810            self.generate_expression(scale)?;
17811        }
17812        self.write(")");
17813        Ok(())
17814    }
17815
17816    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17817        self.write_keyword("CEIL");
17818        self.write("(");
17819        self.generate_expression(&f.this)?;
17820        // Handle Druid-style CEIL(time TO unit) syntax
17821        if let Some(to) = &f.to {
17822            self.write(" ");
17823            self.write_keyword("TO");
17824            self.write(" ");
17825            self.generate_expression(to)?;
17826        } else if let Some(decimals) = &f.decimals {
17827            self.write(", ");
17828            self.generate_expression(decimals)?;
17829        }
17830        self.write(")");
17831        Ok(())
17832    }
17833
17834    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17835        use crate::expressions::Literal;
17836
17837        if let Some(base) = &f.base {
17838            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17839            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17840            if self.is_log_base_none() {
17841                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17842                    self.write_func_name("LOG2");
17843                    self.write("(");
17844                    self.generate_expression(&f.this)?;
17845                    self.write(")");
17846                    return Ok(());
17847                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17848                    self.write_func_name("LOG10");
17849                    self.write("(");
17850                    self.generate_expression(&f.this)?;
17851                    self.write(")");
17852                    return Ok(());
17853                }
17854                // Other bases: fall through to LOG(base, value) — best effort
17855            }
17856
17857            self.write_func_name("LOG");
17858            self.write("(");
17859            if self.is_log_value_first() {
17860                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17861                self.generate_expression(&f.this)?;
17862                self.write(", ");
17863                self.generate_expression(base)?;
17864            } else {
17865                // Default (PostgreSQL, etc.): LOG(base, value)
17866                self.generate_expression(base)?;
17867                self.write(", ");
17868                self.generate_expression(&f.this)?;
17869            }
17870            self.write(")");
17871        } else {
17872            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17873            self.write_func_name("LOG");
17874            self.write("(");
17875            self.generate_expression(&f.this)?;
17876            self.write(")");
17877        }
17878        Ok(())
17879    }
17880
17881    /// Whether the target dialect uses LOG(value, base) order (value first).
17882    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17883    fn is_log_value_first(&self) -> bool {
17884        use crate::dialects::DialectType;
17885        matches!(
17886            self.config.dialect,
17887            Some(DialectType::BigQuery)
17888                | Some(DialectType::TSQL)
17889                | Some(DialectType::Tableau)
17890                | Some(DialectType::Fabric)
17891        )
17892    }
17893
17894    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17895    /// Presto, Trino, ClickHouse, Athena.
17896    fn is_log_base_none(&self) -> bool {
17897        use crate::dialects::DialectType;
17898        matches!(
17899            self.config.dialect,
17900            Some(DialectType::Presto)
17901                | Some(DialectType::Trino)
17902                | Some(DialectType::ClickHouse)
17903                | Some(DialectType::Athena)
17904        )
17905    }
17906
17907    // Date/time function generators
17908
17909    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17910        self.write_keyword("CURRENT_TIME");
17911        if let Some(precision) = f.precision {
17912            self.write(&format!("({})", precision));
17913        }
17914        Ok(())
17915    }
17916
17917    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17918        use crate::dialects::DialectType;
17919
17920        // Oracle/Redshift SYSDATE handling
17921        if f.sysdate {
17922            match self.config.dialect {
17923                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17924                    self.write_keyword("SYSDATE");
17925                    return Ok(());
17926                }
17927                Some(DialectType::Snowflake) => {
17928                    // Snowflake uses SYSDATE() function
17929                    self.write_keyword("SYSDATE");
17930                    self.write("()");
17931                    return Ok(());
17932                }
17933                _ => {
17934                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17935                }
17936            }
17937        }
17938
17939        self.write_keyword("CURRENT_TIMESTAMP");
17940        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17941        if let Some(precision) = f.precision {
17942            self.write(&format!("({})", precision));
17943        } else if matches!(
17944            self.config.dialect,
17945            Some(crate::dialects::DialectType::MySQL)
17946                | Some(crate::dialects::DialectType::SingleStore)
17947                | Some(crate::dialects::DialectType::TiDB)
17948                | Some(crate::dialects::DialectType::Spark)
17949                | Some(crate::dialects::DialectType::Hive)
17950                | Some(crate::dialects::DialectType::Databricks)
17951                | Some(crate::dialects::DialectType::ClickHouse)
17952                | Some(crate::dialects::DialectType::BigQuery)
17953                | Some(crate::dialects::DialectType::Snowflake)
17954        ) {
17955            self.write("()");
17956        }
17957        Ok(())
17958    }
17959
17960    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17961        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17962        if self.config.dialect == Some(DialectType::Exasol) {
17963            self.write_keyword("CONVERT_TZ");
17964            self.write("(");
17965            self.generate_expression(&f.this)?;
17966            self.write(", 'UTC', ");
17967            self.generate_expression(&f.zone)?;
17968            self.write(")");
17969            return Ok(());
17970        }
17971
17972        self.generate_expression(&f.this)?;
17973        self.write_space();
17974        self.write_keyword("AT TIME ZONE");
17975        self.write_space();
17976        self.generate_expression(&f.zone)?;
17977        Ok(())
17978    }
17979
17980    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17981        use crate::dialects::DialectType;
17982
17983        // Presto/Trino use DATE_ADD('unit', interval, date) format
17984        // with the interval cast to BIGINT when needed
17985        let is_presto_like = matches!(
17986            self.config.dialect,
17987            Some(DialectType::Presto) | Some(DialectType::Trino)
17988        );
17989
17990        if is_presto_like {
17991            self.write_keyword(name);
17992            self.write("(");
17993            // Unit as string literal
17994            self.write("'");
17995            self.write_simple_interval_unit(&f.unit, false);
17996            self.write("'");
17997            self.write(", ");
17998            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17999            let needs_cast = !self.returns_integer_type(&f.interval);
18000            if needs_cast {
18001                self.write_keyword("CAST");
18002                self.write("(");
18003            }
18004            self.generate_expression(&f.interval)?;
18005            if needs_cast {
18006                self.write_space();
18007                self.write_keyword("AS");
18008                self.write_space();
18009                self.write_keyword("BIGINT");
18010                self.write(")");
18011            }
18012            self.write(", ");
18013            self.generate_expression(&f.this)?;
18014            self.write(")");
18015        } else {
18016            self.write_keyword(name);
18017            self.write("(");
18018            self.generate_expression(&f.this)?;
18019            self.write(", ");
18020            self.write_keyword("INTERVAL");
18021            self.write_space();
18022            self.generate_expression(&f.interval)?;
18023            self.write_space();
18024            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
18025            self.write(")");
18026        }
18027        Ok(())
18028    }
18029
18030    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
18031    /// This is a heuristic to avoid full type inference
18032    fn returns_integer_type(&self, expr: &Expression) -> bool {
18033        use crate::expressions::{DataType, Literal};
18034        match expr {
18035            // Integer literals (no decimal point)
18036            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
18037
18038            // FLOOR(x) returns integer if x is integer
18039            Expression::Floor(f) => self.returns_integer_type(&f.this),
18040
18041            // ROUND(x) returns integer if x is integer
18042            Expression::Round(f) => {
18043                // Only if no decimals arg or it's returning an integer
18044                f.decimals.is_none() && self.returns_integer_type(&f.this)
18045            }
18046
18047            // SIGN returns integer if input is integer
18048            Expression::Sign(f) => self.returns_integer_type(&f.this),
18049
18050            // ABS returns the same type as input
18051            Expression::Abs(f) => self.returns_integer_type(&f.this),
18052
18053            // Arithmetic operations on integers return integers
18054            Expression::Mul(op) => {
18055                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18056            }
18057            Expression::Add(op) => {
18058                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18059            }
18060            Expression::Sub(op) => {
18061                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18062            }
18063            Expression::Mod(op) => self.returns_integer_type(&op.left),
18064
18065            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
18066            Expression::Cast(c) => matches!(
18067                &c.to,
18068                DataType::BigInt { .. }
18069                    | DataType::Int { .. }
18070                    | DataType::SmallInt { .. }
18071                    | DataType::TinyInt { .. }
18072            ),
18073
18074            // Negation: -x returns integer if x is integer
18075            Expression::Neg(op) => self.returns_integer_type(&op.this),
18076
18077            // Parenthesized expression
18078            Expression::Paren(p) => self.returns_integer_type(&p.this),
18079
18080            // Column references and most expressions are assumed to need casting
18081            // since we don't have full type information
18082            _ => false,
18083        }
18084    }
18085
18086    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18087        self.write_keyword("DATEDIFF");
18088        self.write("(");
18089        if let Some(unit) = &f.unit {
18090            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18091            self.write(", ");
18092        }
18093        self.generate_expression(&f.this)?;
18094        self.write(", ");
18095        self.generate_expression(&f.expression)?;
18096        self.write(")");
18097        Ok(())
18098    }
18099
18100    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18101        self.write_keyword("DATE_TRUNC");
18102        self.write("('");
18103        self.write_datetime_field(&f.unit);
18104        self.write("', ");
18105        self.generate_expression(&f.this)?;
18106        self.write(")");
18107        Ok(())
18108    }
18109
18110    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18111        use crate::dialects::DialectType;
18112        use crate::expressions::DateTimeField;
18113
18114        self.write_keyword("LAST_DAY");
18115        self.write("(");
18116        self.generate_expression(&f.this)?;
18117        if let Some(unit) = &f.unit {
18118            self.write(", ");
18119            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18120            // WEEK(SUNDAY) -> WEEK
18121            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18122                if let DateTimeField::WeekWithModifier(_) = unit {
18123                    self.write_keyword("WEEK");
18124                } else {
18125                    self.write_datetime_field(unit);
18126                }
18127            } else {
18128                self.write_datetime_field(unit);
18129            }
18130        }
18131        self.write(")");
18132        Ok(())
18133    }
18134
18135    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18136        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18137        if matches!(
18138            self.config.dialect,
18139            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18140        ) {
18141            self.write_keyword("DATEPART");
18142            self.write("(");
18143            self.write_datetime_field(&f.field);
18144            self.write(", ");
18145            self.generate_expression(&f.this)?;
18146            self.write(")");
18147            return Ok(());
18148        }
18149        self.write_keyword("EXTRACT");
18150        self.write("(");
18151        // Hive/Spark use lowercase datetime fields in EXTRACT
18152        if matches!(
18153            self.config.dialect,
18154            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18155        ) {
18156            self.write_datetime_field_lower(&f.field);
18157        } else {
18158            self.write_datetime_field(&f.field);
18159        }
18160        self.write_space();
18161        self.write_keyword("FROM");
18162        self.write_space();
18163        self.generate_expression(&f.this)?;
18164        self.write(")");
18165        Ok(())
18166    }
18167
18168    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18169        self.write_keyword("TO_DATE");
18170        self.write("(");
18171        self.generate_expression(&f.this)?;
18172        if let Some(format) = &f.format {
18173            self.write(", ");
18174            self.generate_expression(format)?;
18175        }
18176        self.write(")");
18177        Ok(())
18178    }
18179
18180    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18181        self.write_keyword("TO_TIMESTAMP");
18182        self.write("(");
18183        self.generate_expression(&f.this)?;
18184        if let Some(format) = &f.format {
18185            self.write(", ");
18186            self.generate_expression(format)?;
18187        }
18188        self.write(")");
18189        Ok(())
18190    }
18191
18192    // Control flow function generators
18193
18194    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18195        use crate::dialects::DialectType;
18196
18197        // Generic mode: normalize IF to CASE WHEN
18198        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18199            self.write_keyword("CASE WHEN");
18200            self.write_space();
18201            self.generate_expression(&f.condition)?;
18202            self.write_space();
18203            self.write_keyword("THEN");
18204            self.write_space();
18205            self.generate_expression(&f.true_value)?;
18206            if let Some(false_val) = &f.false_value {
18207                self.write_space();
18208                self.write_keyword("ELSE");
18209                self.write_space();
18210                self.generate_expression(false_val)?;
18211            }
18212            self.write_space();
18213            self.write_keyword("END");
18214            return Ok(());
18215        }
18216
18217        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18218        if self.config.dialect == Some(DialectType::Exasol) {
18219            self.write_keyword("IF");
18220            self.write_space();
18221            self.generate_expression(&f.condition)?;
18222            self.write_space();
18223            self.write_keyword("THEN");
18224            self.write_space();
18225            self.generate_expression(&f.true_value)?;
18226            if let Some(false_val) = &f.false_value {
18227                self.write_space();
18228                self.write_keyword("ELSE");
18229                self.write_space();
18230                self.generate_expression(false_val)?;
18231            }
18232            self.write_space();
18233            self.write_keyword("ENDIF");
18234            return Ok(());
18235        }
18236
18237        // Choose function name based on target dialect
18238        let func_name = match self.config.dialect {
18239            Some(DialectType::Snowflake) => "IFF",
18240            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18241            Some(DialectType::Drill) => "`IF`",
18242            _ => "IF",
18243        };
18244        self.write(func_name);
18245        self.write("(");
18246        self.generate_expression(&f.condition)?;
18247        self.write(", ");
18248        self.generate_expression(&f.true_value)?;
18249        if let Some(false_val) = &f.false_value {
18250            self.write(", ");
18251            self.generate_expression(false_val)?;
18252        }
18253        self.write(")");
18254        Ok(())
18255    }
18256
18257    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18258        self.write_keyword("NVL2");
18259        self.write("(");
18260        self.generate_expression(&f.this)?;
18261        self.write(", ");
18262        self.generate_expression(&f.true_value)?;
18263        self.write(", ");
18264        self.generate_expression(&f.false_value)?;
18265        self.write(")");
18266        Ok(())
18267    }
18268
18269    // Typed aggregate function generators
18270
18271    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18272        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18273        let count_name = match self.config.normalize_functions {
18274            NormalizeFunctions::Upper => "COUNT".to_string(),
18275            NormalizeFunctions::Lower => "count".to_string(),
18276            NormalizeFunctions::None => f
18277                .original_name
18278                .clone()
18279                .unwrap_or_else(|| "COUNT".to_string()),
18280        };
18281        self.write(&count_name);
18282        self.write("(");
18283        if f.distinct {
18284            self.write_keyword("DISTINCT");
18285            self.write_space();
18286        }
18287        if f.star {
18288            self.write("*");
18289        } else if let Some(ref expr) = f.this {
18290            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18291            if let Expression::Tuple(tuple) = expr {
18292                // Check if we need to transform multi-arg COUNT DISTINCT
18293                // When dialect doesn't support multi_arg_distinct, transform:
18294                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18295                let needs_transform =
18296                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18297
18298                if needs_transform {
18299                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18300                    self.write_keyword("CASE");
18301                    for e in &tuple.expressions {
18302                        self.write_space();
18303                        self.write_keyword("WHEN");
18304                        self.write_space();
18305                        self.generate_expression(e)?;
18306                        self.write_space();
18307                        self.write_keyword("IS NULL THEN NULL");
18308                    }
18309                    self.write_space();
18310                    self.write_keyword("ELSE");
18311                    self.write(" (");
18312                    for (i, e) in tuple.expressions.iter().enumerate() {
18313                        if i > 0 {
18314                            self.write(", ");
18315                        }
18316                        self.generate_expression(e)?;
18317                    }
18318                    self.write(")");
18319                    self.write_space();
18320                    self.write_keyword("END");
18321                } else {
18322                    for (i, e) in tuple.expressions.iter().enumerate() {
18323                        if i > 0 {
18324                            self.write(", ");
18325                        }
18326                        self.generate_expression(e)?;
18327                    }
18328                }
18329            } else {
18330                self.generate_expression(expr)?;
18331            }
18332        }
18333        // RESPECT NULLS / IGNORE NULLS
18334        if let Some(ignore) = f.ignore_nulls {
18335            self.write_space();
18336            if ignore {
18337                self.write_keyword("IGNORE NULLS");
18338            } else {
18339                self.write_keyword("RESPECT NULLS");
18340            }
18341        }
18342        self.write(")");
18343        if let Some(ref filter) = f.filter {
18344            self.write_space();
18345            self.write_keyword("FILTER");
18346            self.write("(");
18347            self.write_keyword("WHERE");
18348            self.write_space();
18349            self.generate_expression(filter)?;
18350            self.write(")");
18351        }
18352        Ok(())
18353    }
18354
18355    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18356        // Apply function name normalization based on config
18357        let func_name = match self.config.normalize_functions {
18358            NormalizeFunctions::Upper => name.to_uppercase(),
18359            NormalizeFunctions::Lower => name.to_lowercase(),
18360            NormalizeFunctions::None => {
18361                // Use the original function name from parsing if available,
18362                // otherwise fall back to lowercase of the hardcoded constant
18363                if let Some(ref original) = f.name {
18364                    original.clone()
18365                } else {
18366                    name.to_lowercase()
18367                }
18368            }
18369        };
18370        self.write(&func_name);
18371        self.write("(");
18372        if f.distinct {
18373            self.write_keyword("DISTINCT");
18374            self.write_space();
18375        }
18376        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18377        if !matches!(f.this, Expression::Null(_)) {
18378            self.generate_expression(&f.this)?;
18379        }
18380        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18381        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18382        if self.config.ignore_nulls_in_func
18383            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18384        {
18385            match f.ignore_nulls {
18386                Some(true) => {
18387                    self.write_space();
18388                    self.write_keyword("IGNORE NULLS");
18389                }
18390                Some(false) => {
18391                    self.write_space();
18392                    self.write_keyword("RESPECT NULLS");
18393                }
18394                None => {}
18395            }
18396        }
18397        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18398        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18399        if let Some((ref expr, is_max)) = f.having_max {
18400            self.write_space();
18401            self.write_keyword("HAVING");
18402            self.write_space();
18403            if is_max {
18404                self.write_keyword("MAX");
18405            } else {
18406                self.write_keyword("MIN");
18407            }
18408            self.write_space();
18409            self.generate_expression(expr)?;
18410        }
18411        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18412        if !f.order_by.is_empty() {
18413            self.write_space();
18414            self.write_keyword("ORDER BY");
18415            self.write_space();
18416            for (i, ord) in f.order_by.iter().enumerate() {
18417                if i > 0 {
18418                    self.write(", ");
18419                }
18420                self.generate_ordered(ord)?;
18421            }
18422        }
18423        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18424        if let Some(ref limit) = f.limit {
18425            self.write_space();
18426            self.write_keyword("LIMIT");
18427            self.write_space();
18428            // Check if this is a Tuple representing LIMIT offset, count
18429            if let Expression::Tuple(t) = limit.as_ref() {
18430                if t.expressions.len() == 2 {
18431                    self.generate_expression(&t.expressions[0])?;
18432                    self.write(", ");
18433                    self.generate_expression(&t.expressions[1])?;
18434                } else {
18435                    self.generate_expression(limit)?;
18436                }
18437            } else {
18438                self.generate_expression(limit)?;
18439            }
18440        }
18441        self.write(")");
18442        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18443        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18444        if !self.config.ignore_nulls_in_func
18445            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18446        {
18447            match f.ignore_nulls {
18448                Some(true) => {
18449                    self.write_space();
18450                    self.write_keyword("IGNORE NULLS");
18451                }
18452                Some(false) => {
18453                    self.write_space();
18454                    self.write_keyword("RESPECT NULLS");
18455                }
18456                None => {}
18457            }
18458        }
18459        if let Some(ref filter) = f.filter {
18460            self.write_space();
18461            self.write_keyword("FILTER");
18462            self.write("(");
18463            self.write_keyword("WHERE");
18464            self.write_space();
18465            self.generate_expression(filter)?;
18466            self.write(")");
18467        }
18468        Ok(())
18469    }
18470
18471    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18472        self.write_keyword("GROUP_CONCAT");
18473        self.write("(");
18474        if f.distinct {
18475            self.write_keyword("DISTINCT");
18476            self.write_space();
18477        }
18478        self.generate_expression(&f.this)?;
18479        if let Some(ref order_by) = f.order_by {
18480            self.write_space();
18481            self.write_keyword("ORDER BY");
18482            self.write_space();
18483            for (i, ord) in order_by.iter().enumerate() {
18484                if i > 0 {
18485                    self.write(", ");
18486                }
18487                self.generate_ordered(ord)?;
18488            }
18489        }
18490        if let Some(ref sep) = f.separator {
18491            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18492            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18493            if matches!(
18494                self.config.dialect,
18495                Some(crate::dialects::DialectType::SQLite)
18496            ) {
18497                self.write(", ");
18498                self.generate_expression(sep)?;
18499            } else {
18500                self.write_space();
18501                self.write_keyword("SEPARATOR");
18502                self.write_space();
18503                self.generate_expression(sep)?;
18504            }
18505        }
18506        self.write(")");
18507        if let Some(ref filter) = f.filter {
18508            self.write_space();
18509            self.write_keyword("FILTER");
18510            self.write("(");
18511            self.write_keyword("WHERE");
18512            self.write_space();
18513            self.generate_expression(filter)?;
18514            self.write(")");
18515        }
18516        Ok(())
18517    }
18518
18519    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18520        let is_tsql = matches!(
18521            self.config.dialect,
18522            Some(crate::dialects::DialectType::TSQL)
18523        );
18524        self.write_keyword("STRING_AGG");
18525        self.write("(");
18526        if f.distinct {
18527            self.write_keyword("DISTINCT");
18528            self.write_space();
18529        }
18530        self.generate_expression(&f.this)?;
18531        if let Some(ref separator) = f.separator {
18532            self.write(", ");
18533            self.generate_expression(separator)?;
18534        }
18535        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18536        if !is_tsql {
18537            if let Some(ref order_by) = f.order_by {
18538                self.write_space();
18539                self.write_keyword("ORDER BY");
18540                self.write_space();
18541                for (i, ord) in order_by.iter().enumerate() {
18542                    if i > 0 {
18543                        self.write(", ");
18544                    }
18545                    self.generate_ordered(ord)?;
18546                }
18547            }
18548        }
18549        if let Some(ref limit) = f.limit {
18550            self.write_space();
18551            self.write_keyword("LIMIT");
18552            self.write_space();
18553            self.generate_expression(limit)?;
18554        }
18555        self.write(")");
18556        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18557        if is_tsql {
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        }
18573        if let Some(ref filter) = f.filter {
18574            self.write_space();
18575            self.write_keyword("FILTER");
18576            self.write("(");
18577            self.write_keyword("WHERE");
18578            self.write_space();
18579            self.generate_expression(filter)?;
18580            self.write(")");
18581        }
18582        Ok(())
18583    }
18584
18585    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18586        use crate::dialects::DialectType;
18587        self.write_keyword("LISTAGG");
18588        self.write("(");
18589        if f.distinct {
18590            self.write_keyword("DISTINCT");
18591            self.write_space();
18592        }
18593        self.generate_expression(&f.this)?;
18594        if let Some(ref sep) = f.separator {
18595            self.write(", ");
18596            self.generate_expression(sep)?;
18597        } else if matches!(
18598            self.config.dialect,
18599            Some(DialectType::Trino) | Some(DialectType::Presto)
18600        ) {
18601            // Trino/Presto require explicit separator; default to ','
18602            self.write(", ','");
18603        }
18604        if let Some(ref overflow) = f.on_overflow {
18605            self.write_space();
18606            self.write_keyword("ON OVERFLOW");
18607            self.write_space();
18608            match overflow {
18609                ListAggOverflow::Error => self.write_keyword("ERROR"),
18610                ListAggOverflow::Truncate { filler, with_count } => {
18611                    self.write_keyword("TRUNCATE");
18612                    if let Some(ref fill) = filler {
18613                        self.write_space();
18614                        self.generate_expression(fill)?;
18615                    }
18616                    if *with_count {
18617                        self.write_space();
18618                        self.write_keyword("WITH COUNT");
18619                    } else {
18620                        self.write_space();
18621                        self.write_keyword("WITHOUT COUNT");
18622                    }
18623                }
18624            }
18625        }
18626        self.write(")");
18627        if let Some(ref order_by) = f.order_by {
18628            self.write_space();
18629            self.write_keyword("WITHIN GROUP");
18630            self.write(" (");
18631            self.write_keyword("ORDER BY");
18632            self.write_space();
18633            for (i, ord) in order_by.iter().enumerate() {
18634                if i > 0 {
18635                    self.write(", ");
18636                }
18637                self.generate_ordered(ord)?;
18638            }
18639            self.write(")");
18640        }
18641        if let Some(ref filter) = f.filter {
18642            self.write_space();
18643            self.write_keyword("FILTER");
18644            self.write("(");
18645            self.write_keyword("WHERE");
18646            self.write_space();
18647            self.generate_expression(filter)?;
18648            self.write(")");
18649        }
18650        Ok(())
18651    }
18652
18653    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18654        self.write_keyword("SUM_IF");
18655        self.write("(");
18656        self.generate_expression(&f.this)?;
18657        self.write(", ");
18658        self.generate_expression(&f.condition)?;
18659        self.write(")");
18660        if let Some(ref filter) = f.filter {
18661            self.write_space();
18662            self.write_keyword("FILTER");
18663            self.write("(");
18664            self.write_keyword("WHERE");
18665            self.write_space();
18666            self.generate_expression(filter)?;
18667            self.write(")");
18668        }
18669        Ok(())
18670    }
18671
18672    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18673        self.write_keyword("APPROX_PERCENTILE");
18674        self.write("(");
18675        self.generate_expression(&f.this)?;
18676        self.write(", ");
18677        self.generate_expression(&f.percentile)?;
18678        if let Some(ref acc) = f.accuracy {
18679            self.write(", ");
18680            self.generate_expression(acc)?;
18681        }
18682        self.write(")");
18683        if let Some(ref filter) = f.filter {
18684            self.write_space();
18685            self.write_keyword("FILTER");
18686            self.write("(");
18687            self.write_keyword("WHERE");
18688            self.write_space();
18689            self.generate_expression(filter)?;
18690            self.write(")");
18691        }
18692        Ok(())
18693    }
18694
18695    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18696        self.write_keyword(name);
18697        self.write("(");
18698        self.generate_expression(&f.percentile)?;
18699        self.write(")");
18700        if let Some(ref order_by) = f.order_by {
18701            self.write_space();
18702            self.write_keyword("WITHIN GROUP");
18703            self.write(" (");
18704            self.write_keyword("ORDER BY");
18705            self.write_space();
18706            self.generate_expression(&f.this)?;
18707            for ord in order_by.iter() {
18708                if ord.desc {
18709                    self.write_space();
18710                    self.write_keyword("DESC");
18711                }
18712            }
18713            self.write(")");
18714        }
18715        if let Some(ref filter) = f.filter {
18716            self.write_space();
18717            self.write_keyword("FILTER");
18718            self.write("(");
18719            self.write_keyword("WHERE");
18720            self.write_space();
18721            self.generate_expression(filter)?;
18722            self.write(")");
18723        }
18724        Ok(())
18725    }
18726
18727    // Window function generators
18728
18729    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18730        self.write_keyword("NTILE");
18731        self.write("(");
18732        if let Some(num_buckets) = &f.num_buckets {
18733            self.generate_expression(num_buckets)?;
18734        }
18735        if let Some(order_by) = &f.order_by {
18736            self.write_keyword(" ORDER BY ");
18737            for (i, ob) in order_by.iter().enumerate() {
18738                if i > 0 {
18739                    self.write(", ");
18740                }
18741                self.generate_ordered(ob)?;
18742            }
18743        }
18744        self.write(")");
18745        Ok(())
18746    }
18747
18748    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18749        self.write_keyword(name);
18750        self.write("(");
18751        self.generate_expression(&f.this)?;
18752        if let Some(ref offset) = f.offset {
18753            self.write(", ");
18754            self.generate_expression(offset)?;
18755            if let Some(ref default) = f.default {
18756                self.write(", ");
18757                self.generate_expression(default)?;
18758            }
18759        }
18760        // IGNORE NULLS inside parens for dialects like BigQuery
18761        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18762            self.write_space();
18763            self.write_keyword("IGNORE NULLS");
18764        }
18765        self.write(")");
18766        // IGNORE NULLS outside parens for other dialects
18767        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18768            self.write_space();
18769            self.write_keyword("IGNORE NULLS");
18770        }
18771        Ok(())
18772    }
18773
18774    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18775        self.write_keyword(name);
18776        self.write("(");
18777        self.generate_expression(&f.this)?;
18778        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18779        if self.config.ignore_nulls_in_func {
18780            match f.ignore_nulls {
18781                Some(true) => {
18782                    self.write_space();
18783                    self.write_keyword("IGNORE NULLS");
18784                }
18785                Some(false) => {
18786                    self.write_space();
18787                    self.write_keyword("RESPECT NULLS");
18788                }
18789                None => {}
18790            }
18791        }
18792        self.write(")");
18793        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18794        if !self.config.ignore_nulls_in_func {
18795            match f.ignore_nulls {
18796                Some(true) => {
18797                    self.write_space();
18798                    self.write_keyword("IGNORE NULLS");
18799                }
18800                Some(false) => {
18801                    self.write_space();
18802                    self.write_keyword("RESPECT NULLS");
18803                }
18804                None => {}
18805            }
18806        }
18807        Ok(())
18808    }
18809
18810    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18811        self.write_keyword("NTH_VALUE");
18812        self.write("(");
18813        self.generate_expression(&f.this)?;
18814        self.write(", ");
18815        self.generate_expression(&f.offset)?;
18816        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18817        if self.config.ignore_nulls_in_func {
18818            match f.ignore_nulls {
18819                Some(true) => {
18820                    self.write_space();
18821                    self.write_keyword("IGNORE NULLS");
18822                }
18823                Some(false) => {
18824                    self.write_space();
18825                    self.write_keyword("RESPECT NULLS");
18826                }
18827                None => {}
18828            }
18829        }
18830        self.write(")");
18831        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18832        if matches!(
18833            self.config.dialect,
18834            Some(crate::dialects::DialectType::Snowflake)
18835        ) {
18836            match f.from_first {
18837                Some(true) => {
18838                    self.write_space();
18839                    self.write_keyword("FROM FIRST");
18840                }
18841                Some(false) => {
18842                    self.write_space();
18843                    self.write_keyword("FROM LAST");
18844                }
18845                None => {}
18846            }
18847        }
18848        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18849        if !self.config.ignore_nulls_in_func {
18850            match f.ignore_nulls {
18851                Some(true) => {
18852                    self.write_space();
18853                    self.write_keyword("IGNORE NULLS");
18854                }
18855                Some(false) => {
18856                    self.write_space();
18857                    self.write_keyword("RESPECT NULLS");
18858                }
18859                None => {}
18860            }
18861        }
18862        Ok(())
18863    }
18864
18865    // Additional string function generators
18866
18867    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18868        // Standard syntax: POSITION(substr IN str)
18869        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18870        if matches!(
18871            self.config.dialect,
18872            Some(crate::dialects::DialectType::ClickHouse)
18873        ) {
18874            self.write_keyword("POSITION");
18875            self.write("(");
18876            self.generate_expression(&f.string)?;
18877            self.write(", ");
18878            self.generate_expression(&f.substring)?;
18879            if let Some(ref start) = f.start {
18880                self.write(", ");
18881                self.generate_expression(start)?;
18882            }
18883            self.write(")");
18884            return Ok(());
18885        }
18886
18887        self.write_keyword("POSITION");
18888        self.write("(");
18889        self.generate_expression(&f.substring)?;
18890        self.write_space();
18891        self.write_keyword("IN");
18892        self.write_space();
18893        self.generate_expression(&f.string)?;
18894        if let Some(ref start) = f.start {
18895            self.write(", ");
18896            self.generate_expression(start)?;
18897        }
18898        self.write(")");
18899        Ok(())
18900    }
18901
18902    // Additional math function generators
18903
18904    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18905        // Teradata RANDOM(lower, upper)
18906        if f.lower.is_some() || f.upper.is_some() {
18907            self.write_keyword("RANDOM");
18908            self.write("(");
18909            if let Some(ref lower) = f.lower {
18910                self.generate_expression(lower)?;
18911            }
18912            if let Some(ref upper) = f.upper {
18913                self.write(", ");
18914                self.generate_expression(upper)?;
18915            }
18916            self.write(")");
18917            return Ok(());
18918        }
18919        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18920        let func_name = match self.config.dialect {
18921            Some(crate::dialects::DialectType::Snowflake)
18922            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18923            _ => "RAND",
18924        };
18925        self.write_keyword(func_name);
18926        self.write("(");
18927        // DuckDB doesn't support seeded RANDOM, so skip the seed
18928        if !matches!(
18929            self.config.dialect,
18930            Some(crate::dialects::DialectType::DuckDB)
18931        ) {
18932            if let Some(ref seed) = f.seed {
18933                self.generate_expression(seed)?;
18934            }
18935        }
18936        self.write(")");
18937        Ok(())
18938    }
18939
18940    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18941        self.write_keyword("TRUNCATE");
18942        self.write("(");
18943        self.generate_expression(&f.this)?;
18944        if let Some(ref decimals) = f.decimals {
18945            self.write(", ");
18946            self.generate_expression(decimals)?;
18947        }
18948        self.write(")");
18949        Ok(())
18950    }
18951
18952    // Control flow generators
18953
18954    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18955        self.write_keyword("DECODE");
18956        self.write("(");
18957        self.generate_expression(&f.this)?;
18958        for (search, result) in &f.search_results {
18959            self.write(", ");
18960            self.generate_expression(search)?;
18961            self.write(", ");
18962            self.generate_expression(result)?;
18963        }
18964        if let Some(ref default) = f.default {
18965            self.write(", ");
18966            self.generate_expression(default)?;
18967        }
18968        self.write(")");
18969        Ok(())
18970    }
18971
18972    // Date/time function generators
18973
18974    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18975        self.write_keyword(name);
18976        self.write("(");
18977        self.generate_expression(&f.this)?;
18978        self.write(", ");
18979        self.generate_expression(&f.format)?;
18980        self.write(")");
18981        Ok(())
18982    }
18983
18984    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18985        self.write_keyword("FROM_UNIXTIME");
18986        self.write("(");
18987        self.generate_expression(&f.this)?;
18988        if let Some(ref format) = f.format {
18989            self.write(", ");
18990            self.generate_expression(format)?;
18991        }
18992        self.write(")");
18993        Ok(())
18994    }
18995
18996    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18997        self.write_keyword("UNIX_TIMESTAMP");
18998        self.write("(");
18999        if let Some(ref expr) = f.this {
19000            self.generate_expression(expr)?;
19001            if let Some(ref format) = f.format {
19002                self.write(", ");
19003                self.generate_expression(format)?;
19004            }
19005        } else if matches!(
19006            self.config.dialect,
19007            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
19008        ) {
19009            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
19010            self.write_keyword("CURRENT_TIMESTAMP");
19011            self.write("()");
19012        }
19013        self.write(")");
19014        Ok(())
19015    }
19016
19017    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
19018        self.write_keyword("MAKE_DATE");
19019        self.write("(");
19020        self.generate_expression(&f.year)?;
19021        self.write(", ");
19022        self.generate_expression(&f.month)?;
19023        self.write(", ");
19024        self.generate_expression(&f.day)?;
19025        self.write(")");
19026        Ok(())
19027    }
19028
19029    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
19030        self.write_keyword("MAKE_TIMESTAMP");
19031        self.write("(");
19032        self.generate_expression(&f.year)?;
19033        self.write(", ");
19034        self.generate_expression(&f.month)?;
19035        self.write(", ");
19036        self.generate_expression(&f.day)?;
19037        self.write(", ");
19038        self.generate_expression(&f.hour)?;
19039        self.write(", ");
19040        self.generate_expression(&f.minute)?;
19041        self.write(", ");
19042        self.generate_expression(&f.second)?;
19043        if let Some(ref tz) = f.timezone {
19044            self.write(", ");
19045            self.generate_expression(tz)?;
19046        }
19047        self.write(")");
19048        Ok(())
19049    }
19050
19051    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
19052    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
19053        match expr {
19054            Expression::Struct(s) => {
19055                if s.fields.iter().all(|(name, _)| name.is_some()) {
19056                    Some(
19057                        s.fields
19058                            .iter()
19059                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
19060                            .collect(),
19061                    )
19062                } else {
19063                    None
19064                }
19065            }
19066            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19067                // Check if all args are Alias (named fields)
19068                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
19069                    Some(
19070                        f.args
19071                            .iter()
19072                            .filter_map(|a| {
19073                                if let Expression::Alias(alias) = a {
19074                                    Some(alias.alias.name.clone())
19075                                } else {
19076                                    None
19077                                }
19078                            })
19079                            .collect(),
19080                    )
19081                } else {
19082                    None
19083                }
19084            }
19085            _ => None,
19086        }
19087    }
19088
19089    /// Check if a struct expression has any unnamed fields
19090    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
19091        match expr {
19092            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
19093            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19094                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
19095            }
19096            _ => false,
19097        }
19098    }
19099
19100    /// Get the field count of a struct expression
19101    fn struct_field_count(expr: &Expression) -> usize {
19102        match expr {
19103            Expression::Struct(s) => s.fields.len(),
19104            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
19105            _ => 0,
19106        }
19107    }
19108
19109    /// Apply field names to an unnamed struct expression, producing a new expression with names
19110    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
19111        match expr {
19112            Expression::Struct(s) => {
19113                let mut new_fields = Vec::with_capacity(s.fields.len());
19114                for (i, (name, value)) in s.fields.iter().enumerate() {
19115                    if name.is_none() && i < field_names.len() {
19116                        new_fields.push((Some(field_names[i].clone()), value.clone()));
19117                    } else {
19118                        new_fields.push((name.clone(), value.clone()));
19119                    }
19120                }
19121                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
19122            }
19123            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
19124                let mut new_args = Vec::with_capacity(f.args.len());
19125                for (i, arg) in f.args.iter().enumerate() {
19126                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
19127                        // Wrap the value in an Alias with the inherited name
19128                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19129                            this: arg.clone(),
19130                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19131                            column_aliases: Vec::new(),
19132                            pre_alias_comments: Vec::new(),
19133                            trailing_comments: Vec::new(),
19134                            inferred_type: None,
19135                        })));
19136                    } else {
19137                        new_args.push(arg.clone());
19138                    }
19139                }
19140                Expression::Function(Box::new(crate::expressions::Function {
19141                    name: f.name.clone(),
19142                    args: new_args,
19143                    distinct: f.distinct,
19144                    trailing_comments: f.trailing_comments.clone(),
19145                    use_bracket_syntax: f.use_bracket_syntax,
19146                    no_parens: f.no_parens,
19147                    quoted: f.quoted,
19148                    span: None,
19149                    inferred_type: None,
19150                }))
19151            }
19152            _ => expr.clone(),
19153        }
19154    }
19155
19156    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19157    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19158    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19159    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19160        let first = match expressions.first() {
19161            Some(e) => e,
19162            None => return expressions.to_vec(),
19163        };
19164
19165        let field_names = match Self::extract_struct_field_names(first) {
19166            Some(names) if !names.is_empty() => names,
19167            _ => return expressions.to_vec(),
19168        };
19169
19170        let mut result = Vec::with_capacity(expressions.len());
19171        for (idx, expr) in expressions.iter().enumerate() {
19172            if idx == 0 {
19173                result.push(expr.clone());
19174                continue;
19175            }
19176            // Check if this is a struct with unnamed fields that needs name propagation
19177            if Self::struct_field_count(expr) == field_names.len()
19178                && Self::struct_has_unnamed_fields(expr)
19179            {
19180                result.push(Self::apply_struct_field_names(expr, &field_names));
19181            } else {
19182                result.push(expr.clone());
19183            }
19184        }
19185        result
19186    }
19187
19188    // Array function generators
19189
19190    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19191        // Apply struct name inheritance for target dialects that need it
19192        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19193        let needs_inheritance = matches!(
19194            self.config.dialect,
19195            Some(DialectType::DuckDB)
19196                | Some(DialectType::Spark)
19197                | Some(DialectType::Databricks)
19198                | Some(DialectType::Hive)
19199                | Some(DialectType::Snowflake)
19200                | Some(DialectType::Presto)
19201                | Some(DialectType::Trino)
19202        );
19203        let propagated: Vec<Expression>;
19204        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19205            propagated = Self::inherit_struct_field_names(&f.expressions);
19206            &propagated
19207        } else {
19208            &f.expressions
19209        };
19210
19211        // Check if elements should be split onto multiple lines (pretty + too wide)
19212        let should_split = if self.config.pretty && !expressions.is_empty() {
19213            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19214            for expr in expressions {
19215                let mut temp_gen = Generator::with_config(self.config.clone());
19216                temp_gen.config.pretty = false;
19217                temp_gen.generate_expression(expr)?;
19218                expr_strings.push(temp_gen.output);
19219            }
19220            self.too_wide(&expr_strings)
19221        } else {
19222            false
19223        };
19224
19225        if f.bracket_notation {
19226            // For Spark/Databricks, use ARRAY(...) with parens
19227            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19228            // For others (DuckDB, Snowflake), use bare [...]
19229            let (open, close) = match self.config.dialect {
19230                None
19231                | Some(DialectType::Generic)
19232                | Some(DialectType::Spark)
19233                | Some(DialectType::Databricks)
19234                | Some(DialectType::Hive) => {
19235                    self.write_keyword("ARRAY");
19236                    ("(", ")")
19237                }
19238                Some(DialectType::Presto)
19239                | Some(DialectType::Trino)
19240                | Some(DialectType::PostgreSQL)
19241                | Some(DialectType::Redshift)
19242                | Some(DialectType::Materialize)
19243                | Some(DialectType::RisingWave)
19244                | Some(DialectType::CockroachDB) => {
19245                    self.write_keyword("ARRAY");
19246                    ("[", "]")
19247                }
19248                _ => ("[", "]"),
19249            };
19250            self.write(open);
19251            if should_split {
19252                self.write_newline();
19253                self.indent_level += 1;
19254                for (i, expr) in expressions.iter().enumerate() {
19255                    self.write_indent();
19256                    self.generate_expression(expr)?;
19257                    if i + 1 < expressions.len() {
19258                        self.write(",");
19259                    }
19260                    self.write_newline();
19261                }
19262                self.indent_level -= 1;
19263                self.write_indent();
19264            } else {
19265                for (i, expr) in expressions.iter().enumerate() {
19266                    if i > 0 {
19267                        self.write(", ");
19268                    }
19269                    self.generate_expression(expr)?;
19270                }
19271            }
19272            self.write(close);
19273        } else {
19274            // Use LIST keyword if that was the original syntax (DuckDB)
19275            if f.use_list_keyword {
19276                self.write_keyword("LIST");
19277            } else {
19278                self.write_keyword("ARRAY");
19279            }
19280            // For Spark/Hive, always use ARRAY(...) with parens
19281            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19282            let has_subquery = expressions
19283                .iter()
19284                .any(|e| matches!(e, Expression::Select(_)));
19285            let (open, close) = if matches!(
19286                self.config.dialect,
19287                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19288            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19289                && has_subquery)
19290            {
19291                ("(", ")")
19292            } else {
19293                ("[", "]")
19294            };
19295            self.write(open);
19296            if should_split {
19297                self.write_newline();
19298                self.indent_level += 1;
19299                for (i, expr) in expressions.iter().enumerate() {
19300                    self.write_indent();
19301                    self.generate_expression(expr)?;
19302                    if i + 1 < expressions.len() {
19303                        self.write(",");
19304                    }
19305                    self.write_newline();
19306                }
19307                self.indent_level -= 1;
19308                self.write_indent();
19309            } else {
19310                for (i, expr) in expressions.iter().enumerate() {
19311                    if i > 0 {
19312                        self.write(", ");
19313                    }
19314                    self.generate_expression(expr)?;
19315                }
19316            }
19317            self.write(close);
19318        }
19319        Ok(())
19320    }
19321
19322    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19323        self.write_keyword("ARRAY_SORT");
19324        self.write("(");
19325        self.generate_expression(&f.this)?;
19326        if let Some(ref comp) = f.comparator {
19327            self.write(", ");
19328            self.generate_expression(comp)?;
19329        }
19330        self.write(")");
19331        Ok(())
19332    }
19333
19334    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19335        self.write_keyword(name);
19336        self.write("(");
19337        self.generate_expression(&f.this)?;
19338        self.write(", ");
19339        self.generate_expression(&f.separator)?;
19340        if let Some(ref null_rep) = f.null_replacement {
19341            self.write(", ");
19342            self.generate_expression(null_rep)?;
19343        }
19344        self.write(")");
19345        Ok(())
19346    }
19347
19348    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19349        self.write_keyword("UNNEST");
19350        self.write("(");
19351        self.generate_expression(&f.this)?;
19352        for extra in &f.expressions {
19353            self.write(", ");
19354            self.generate_expression(extra)?;
19355        }
19356        self.write(")");
19357        if f.with_ordinality {
19358            self.write_space();
19359            if self.config.unnest_with_ordinality {
19360                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19361                self.write_keyword("WITH ORDINALITY");
19362            } else if f.offset_alias.is_some() {
19363                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19364                // Alias (if any) comes BEFORE WITH OFFSET
19365                if let Some(ref alias) = f.alias {
19366                    self.write_keyword("AS");
19367                    self.write_space();
19368                    self.generate_identifier(alias)?;
19369                    self.write_space();
19370                }
19371                self.write_keyword("WITH OFFSET");
19372                if let Some(ref offset_alias) = f.offset_alias {
19373                    self.write_space();
19374                    self.write_keyword("AS");
19375                    self.write_space();
19376                    self.generate_identifier(offset_alias)?;
19377                }
19378            } else {
19379                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19380                self.write_keyword("WITH OFFSET");
19381                if f.alias.is_none() {
19382                    self.write(" AS offset");
19383                }
19384            }
19385        }
19386        if let Some(ref alias) = f.alias {
19387            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19388            let should_add_alias = if !f.with_ordinality {
19389                true
19390            } else if self.config.unnest_with_ordinality {
19391                // Presto/Trino: alias comes after WITH ORDINALITY
19392                true
19393            } else if f.offset_alias.is_some() {
19394                // BigQuery expansion: alias already handled above
19395                false
19396            } else {
19397                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19398                true
19399            };
19400            if should_add_alias {
19401                self.write_space();
19402                self.write_keyword("AS");
19403                self.write_space();
19404                self.generate_identifier(alias)?;
19405            }
19406        }
19407        Ok(())
19408    }
19409
19410    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19411        self.write_keyword("FILTER");
19412        self.write("(");
19413        self.generate_expression(&f.this)?;
19414        self.write(", ");
19415        self.generate_expression(&f.filter)?;
19416        self.write(")");
19417        Ok(())
19418    }
19419
19420    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19421        self.write_keyword("TRANSFORM");
19422        self.write("(");
19423        self.generate_expression(&f.this)?;
19424        self.write(", ");
19425        self.generate_expression(&f.transform)?;
19426        self.write(")");
19427        Ok(())
19428    }
19429
19430    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19431        self.write_keyword(name);
19432        self.write("(");
19433        self.generate_expression(&f.start)?;
19434        self.write(", ");
19435        self.generate_expression(&f.stop)?;
19436        if let Some(ref step) = f.step {
19437            self.write(", ");
19438            self.generate_expression(step)?;
19439        }
19440        self.write(")");
19441        Ok(())
19442    }
19443
19444    // Struct function generators
19445
19446    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19447        self.write_keyword("STRUCT");
19448        self.write("(");
19449        for (i, (name, expr)) in f.fields.iter().enumerate() {
19450            if i > 0 {
19451                self.write(", ");
19452            }
19453            if let Some(ref id) = name {
19454                self.generate_identifier(id)?;
19455                self.write(" ");
19456                self.write_keyword("AS");
19457                self.write(" ");
19458            }
19459            self.generate_expression(expr)?;
19460        }
19461        self.write(")");
19462        Ok(())
19463    }
19464
19465    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19466    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19467        // Extract named/unnamed fields from function args
19468        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19469        let mut names: Vec<Option<String>> = Vec::new();
19470        let mut values: Vec<&Expression> = Vec::new();
19471        let mut all_named = true;
19472
19473        for arg in &func.args {
19474            match arg {
19475                Expression::Alias(a) => {
19476                    names.push(Some(a.alias.name.clone()));
19477                    values.push(&a.this);
19478                }
19479                _ => {
19480                    names.push(None);
19481                    values.push(arg);
19482                    all_named = false;
19483                }
19484            }
19485        }
19486
19487        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19488            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19489            self.write("{");
19490            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19491                if i > 0 {
19492                    self.write(", ");
19493                }
19494                if let Some(n) = name {
19495                    self.write("'");
19496                    self.write(n);
19497                    self.write("'");
19498                } else {
19499                    self.write("'_");
19500                    self.write(&i.to_string());
19501                    self.write("'");
19502                }
19503                self.write(": ");
19504                self.generate_expression(value)?;
19505            }
19506            self.write("}");
19507            return Ok(());
19508        }
19509
19510        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19511            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19512            self.write_keyword("OBJECT_CONSTRUCT");
19513            self.write("(");
19514            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19515                if i > 0 {
19516                    self.write(", ");
19517                }
19518                if let Some(n) = name {
19519                    self.write("'");
19520                    self.write(n);
19521                    self.write("'");
19522                } else {
19523                    self.write("'_");
19524                    self.write(&i.to_string());
19525                    self.write("'");
19526                }
19527                self.write(", ");
19528                self.generate_expression(value)?;
19529            }
19530            self.write(")");
19531            return Ok(());
19532        }
19533
19534        if matches!(
19535            self.config.dialect,
19536            Some(DialectType::Presto) | Some(DialectType::Trino)
19537        ) {
19538            if all_named && !names.is_empty() {
19539                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19540                // Need to infer types from values
19541                self.write_keyword("CAST");
19542                self.write("(");
19543                self.write_keyword("ROW");
19544                self.write("(");
19545                for (i, value) in values.iter().enumerate() {
19546                    if i > 0 {
19547                        self.write(", ");
19548                    }
19549                    self.generate_expression(value)?;
19550                }
19551                self.write(")");
19552                self.write(" ");
19553                self.write_keyword("AS");
19554                self.write(" ");
19555                self.write_keyword("ROW");
19556                self.write("(");
19557                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19558                    if i > 0 {
19559                        self.write(", ");
19560                    }
19561                    if let Some(n) = name {
19562                        self.write(n);
19563                    }
19564                    self.write(" ");
19565                    let type_str = Self::infer_sql_type_for_presto(value);
19566                    self.write_keyword(&type_str);
19567                }
19568                self.write(")");
19569                self.write(")");
19570            } else {
19571                // Unnamed: ROW(values...)
19572                self.write_keyword("ROW");
19573                self.write("(");
19574                for (i, value) in values.iter().enumerate() {
19575                    if i > 0 {
19576                        self.write(", ");
19577                    }
19578                    self.generate_expression(value)?;
19579                }
19580                self.write(")");
19581            }
19582            return Ok(());
19583        }
19584
19585        // Default: ROW(values...) for other dialects
19586        self.write_keyword("ROW");
19587        self.write("(");
19588        for (i, value) in values.iter().enumerate() {
19589            if i > 0 {
19590                self.write(", ");
19591            }
19592            self.generate_expression(value)?;
19593        }
19594        self.write(")");
19595        Ok(())
19596    }
19597
19598    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19599    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19600        match expr {
19601            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19602            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19603                if n.contains('.') {
19604                    "DOUBLE".to_string()
19605                } else {
19606                    "INTEGER".to_string()
19607                }
19608            }
19609            Expression::Boolean(_) => "BOOLEAN".to_string(),
19610            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19611            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19612                "TIMESTAMP".to_string()
19613            }
19614            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19615                "TIMESTAMP".to_string()
19616            }
19617            Expression::Array(_) | Expression::ArrayFunc(_) => {
19618                // Try to infer element type from first element
19619                "ARRAY(VARCHAR)".to_string()
19620            }
19621            // For nested structs - generate a nested ROW type by inspecting fields
19622            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19623            Expression::Function(f) => {
19624                let up = f.name.to_uppercase();
19625                if up == "STRUCT" {
19626                    "ROW".to_string()
19627                } else if up == "CURRENT_DATE" {
19628                    "DATE".to_string()
19629                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19630                    "TIMESTAMP".to_string()
19631                } else {
19632                    "VARCHAR".to_string()
19633                }
19634            }
19635            _ => "VARCHAR".to_string(),
19636        }
19637    }
19638
19639    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19640        // DuckDB uses STRUCT_EXTRACT function syntax
19641        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19642            self.write_keyword("STRUCT_EXTRACT");
19643            self.write("(");
19644            self.generate_expression(&f.this)?;
19645            self.write(", ");
19646            // Output field name as string literal
19647            self.write("'");
19648            self.write(&f.field.name);
19649            self.write("'");
19650            self.write(")");
19651            return Ok(());
19652        }
19653        self.generate_expression(&f.this)?;
19654        self.write(".");
19655        self.generate_identifier(&f.field)
19656    }
19657
19658    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19659        self.write_keyword("NAMED_STRUCT");
19660        self.write("(");
19661        for (i, (name, value)) in f.pairs.iter().enumerate() {
19662            if i > 0 {
19663                self.write(", ");
19664            }
19665            self.generate_expression(name)?;
19666            self.write(", ");
19667            self.generate_expression(value)?;
19668        }
19669        self.write(")");
19670        Ok(())
19671    }
19672
19673    // Map function generators
19674
19675    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19676        if f.curly_brace_syntax {
19677            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19678            if f.with_map_keyword {
19679                self.write_keyword("MAP");
19680                self.write(" ");
19681            }
19682            self.write("{");
19683            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19684                if i > 0 {
19685                    self.write(", ");
19686                }
19687                self.generate_expression(key)?;
19688                self.write(": ");
19689                self.generate_expression(val)?;
19690            }
19691            self.write("}");
19692        } else {
19693            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19694            self.write_keyword("MAP");
19695            self.write("(");
19696            self.write_keyword("ARRAY");
19697            self.write("[");
19698            for (i, key) in f.keys.iter().enumerate() {
19699                if i > 0 {
19700                    self.write(", ");
19701                }
19702                self.generate_expression(key)?;
19703            }
19704            self.write("], ");
19705            self.write_keyword("ARRAY");
19706            self.write("[");
19707            for (i, val) in f.values.iter().enumerate() {
19708                if i > 0 {
19709                    self.write(", ");
19710                }
19711                self.generate_expression(val)?;
19712            }
19713            self.write("])");
19714        }
19715        Ok(())
19716    }
19717
19718    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19719        self.write_keyword(name);
19720        self.write("(");
19721        self.generate_expression(&f.this)?;
19722        self.write(", ");
19723        self.generate_expression(&f.transform)?;
19724        self.write(")");
19725        Ok(())
19726    }
19727
19728    // JSON function generators
19729
19730    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19731        use crate::dialects::DialectType;
19732
19733        // Check if we should use arrow syntax (-> or ->>)
19734        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19735
19736        if use_arrow {
19737            // Output arrow syntax: expr -> path or expr ->> path
19738            self.generate_expression(&f.this)?;
19739            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19740                self.write(" ->> ");
19741            } else {
19742                self.write(" -> ");
19743            }
19744            self.generate_expression(&f.path)?;
19745            return Ok(());
19746        }
19747
19748        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19749        if f.hash_arrow_syntax
19750            && matches!(
19751                self.config.dialect,
19752                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19753            )
19754        {
19755            self.generate_expression(&f.this)?;
19756            self.write(" #>> ");
19757            self.generate_expression(&f.path)?;
19758            return Ok(());
19759        }
19760
19761        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19762        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19763        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19764            match name {
19765                "JSON_EXTRACT_SCALAR"
19766                | "JSON_EXTRACT_PATH_TEXT"
19767                | "JSON_EXTRACT"
19768                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19769                _ => name,
19770            }
19771        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19772            match name {
19773                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19774                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19775                _ => name,
19776            }
19777        } else {
19778            name
19779        };
19780
19781        self.write_keyword(func_name);
19782        self.write("(");
19783        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19784        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19785            if let Expression::Cast(ref cast) = f.this {
19786                if matches!(cast.to, crate::expressions::DataType::Json) {
19787                    self.generate_expression(&cast.this)?;
19788                } else {
19789                    self.generate_expression(&f.this)?;
19790                }
19791            } else {
19792                self.generate_expression(&f.this)?;
19793            }
19794        } else {
19795            self.generate_expression(&f.this)?;
19796        }
19797        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19798        // decompose JSON path into separate string arguments
19799        if matches!(
19800            self.config.dialect,
19801            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19802        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19803        {
19804            if let Expression::Literal(Literal::String(ref s)) = f.path {
19805                let parts = Self::decompose_json_path(s);
19806                for part in &parts {
19807                    self.write(", '");
19808                    self.write(part);
19809                    self.write("'");
19810                }
19811            } else {
19812                self.write(", ");
19813                self.generate_expression(&f.path)?;
19814            }
19815        } else {
19816            self.write(", ");
19817            self.generate_expression(&f.path)?;
19818        }
19819
19820        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19821        // These go BEFORE the closing parenthesis
19822        if let Some(ref wrapper) = f.wrapper_option {
19823            self.write_space();
19824            self.write_keyword(wrapper);
19825        }
19826        if let Some(ref quotes) = f.quotes_option {
19827            self.write_space();
19828            self.write_keyword(quotes);
19829            if f.on_scalar_string {
19830                self.write_space();
19831                self.write_keyword("ON SCALAR STRING");
19832            }
19833        }
19834        if let Some(ref on_err) = f.on_error {
19835            self.write_space();
19836            self.write_keyword(on_err);
19837        }
19838        if let Some(ref ret_type) = f.returning {
19839            self.write_space();
19840            self.write_keyword("RETURNING");
19841            self.write_space();
19842            self.generate_data_type(ret_type)?;
19843        }
19844
19845        self.write(")");
19846        Ok(())
19847    }
19848
19849    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19850    fn dialect_supports_json_arrow(&self) -> bool {
19851        use crate::dialects::DialectType;
19852        match self.config.dialect {
19853            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19854            Some(DialectType::PostgreSQL) => true,
19855            Some(DialectType::MySQL) => true,
19856            Some(DialectType::DuckDB) => true,
19857            Some(DialectType::CockroachDB) => true,
19858            Some(DialectType::StarRocks) => true,
19859            Some(DialectType::SQLite) => true,
19860            // Other dialects use function syntax
19861            _ => false,
19862        }
19863    }
19864
19865    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19866        use crate::dialects::DialectType;
19867
19868        // PostgreSQL uses #> operator for JSONB path extraction
19869        if matches!(
19870            self.config.dialect,
19871            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19872        ) && name == "JSON_EXTRACT_PATH"
19873        {
19874            self.generate_expression(&f.this)?;
19875            self.write(" #> ");
19876            if f.paths.len() == 1 {
19877                self.generate_expression(&f.paths[0])?;
19878            } else {
19879                // Multiple paths: ARRAY[path1, path2, ...]
19880                self.write_keyword("ARRAY");
19881                self.write("[");
19882                for (i, path) in f.paths.iter().enumerate() {
19883                    if i > 0 {
19884                        self.write(", ");
19885                    }
19886                    self.generate_expression(path)?;
19887                }
19888                self.write("]");
19889            }
19890            return Ok(());
19891        }
19892
19893        self.write_keyword(name);
19894        self.write("(");
19895        self.generate_expression(&f.this)?;
19896        for path in &f.paths {
19897            self.write(", ");
19898            self.generate_expression(path)?;
19899        }
19900        self.write(")");
19901        Ok(())
19902    }
19903
19904    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19905        use crate::dialects::DialectType;
19906
19907        self.write_keyword("JSON_OBJECT");
19908        self.write("(");
19909        if f.star {
19910            self.write("*");
19911        } else {
19912            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19913            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19914            // Also respect the json_key_value_pair_sep config
19915            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19916                || matches!(
19917                    self.config.dialect,
19918                    Some(DialectType::BigQuery)
19919                        | Some(DialectType::MySQL)
19920                        | Some(DialectType::SQLite)
19921                );
19922
19923            for (i, (key, value)) in f.pairs.iter().enumerate() {
19924                if i > 0 {
19925                    self.write(", ");
19926                }
19927                self.generate_expression(key)?;
19928                if use_comma_syntax {
19929                    self.write(", ");
19930                } else {
19931                    self.write(": ");
19932                }
19933                self.generate_expression(value)?;
19934            }
19935        }
19936        if let Some(null_handling) = f.null_handling {
19937            self.write_space();
19938            match null_handling {
19939                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19940                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19941            }
19942        }
19943        if f.with_unique_keys {
19944            self.write_space();
19945            self.write_keyword("WITH UNIQUE KEYS");
19946        }
19947        if let Some(ref ret_type) = f.returning_type {
19948            self.write_space();
19949            self.write_keyword("RETURNING");
19950            self.write_space();
19951            self.generate_data_type(ret_type)?;
19952            if f.format_json {
19953                self.write_space();
19954                self.write_keyword("FORMAT JSON");
19955            }
19956            if let Some(ref enc) = f.encoding {
19957                self.write_space();
19958                self.write_keyword("ENCODING");
19959                self.write_space();
19960                self.write(enc);
19961            }
19962        }
19963        self.write(")");
19964        Ok(())
19965    }
19966
19967    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19968        self.write_keyword(name);
19969        self.write("(");
19970        self.generate_expression(&f.this)?;
19971        for (path, value) in &f.path_values {
19972            self.write(", ");
19973            self.generate_expression(path)?;
19974            self.write(", ");
19975            self.generate_expression(value)?;
19976        }
19977        self.write(")");
19978        Ok(())
19979    }
19980
19981    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19982        self.write_keyword("JSON_ARRAYAGG");
19983        self.write("(");
19984        self.generate_expression(&f.this)?;
19985        if let Some(ref order_by) = f.order_by {
19986            self.write_space();
19987            self.write_keyword("ORDER BY");
19988            self.write_space();
19989            for (i, ord) in order_by.iter().enumerate() {
19990                if i > 0 {
19991                    self.write(", ");
19992                }
19993                self.generate_ordered(ord)?;
19994            }
19995        }
19996        if let Some(null_handling) = f.null_handling {
19997            self.write_space();
19998            match null_handling {
19999                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20000                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20001            }
20002        }
20003        self.write(")");
20004        if let Some(ref filter) = f.filter {
20005            self.write_space();
20006            self.write_keyword("FILTER");
20007            self.write("(");
20008            self.write_keyword("WHERE");
20009            self.write_space();
20010            self.generate_expression(filter)?;
20011            self.write(")");
20012        }
20013        Ok(())
20014    }
20015
20016    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
20017        self.write_keyword("JSON_OBJECTAGG");
20018        self.write("(");
20019        self.generate_expression(&f.key)?;
20020        self.write(": ");
20021        self.generate_expression(&f.value)?;
20022        if let Some(null_handling) = f.null_handling {
20023            self.write_space();
20024            match null_handling {
20025                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20026                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20027            }
20028        }
20029        self.write(")");
20030        if let Some(ref filter) = f.filter {
20031            self.write_space();
20032            self.write_keyword("FILTER");
20033            self.write("(");
20034            self.write_keyword("WHERE");
20035            self.write_space();
20036            self.generate_expression(filter)?;
20037            self.write(")");
20038        }
20039        Ok(())
20040    }
20041
20042    // Type casting/conversion generators
20043
20044    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
20045        use crate::dialects::DialectType;
20046
20047        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
20048        if self.config.dialect == Some(DialectType::Redshift) {
20049            self.write_keyword("CAST");
20050            self.write("(");
20051            self.generate_expression(&f.this)?;
20052            self.write_space();
20053            self.write_keyword("AS");
20054            self.write_space();
20055            self.generate_data_type(&f.to)?;
20056            self.write(")");
20057            return Ok(());
20058        }
20059
20060        self.write_keyword("CONVERT");
20061        self.write("(");
20062        self.generate_data_type(&f.to)?;
20063        self.write(", ");
20064        self.generate_expression(&f.this)?;
20065        if let Some(ref style) = f.style {
20066            self.write(", ");
20067            self.generate_expression(style)?;
20068        }
20069        self.write(")");
20070        Ok(())
20071    }
20072
20073    // Additional expression generators
20074
20075    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
20076        if f.colon {
20077            // DuckDB syntax: LAMBDA x : expr
20078            self.write_keyword("LAMBDA");
20079            self.write_space();
20080            for (i, param) in f.parameters.iter().enumerate() {
20081                if i > 0 {
20082                    self.write(", ");
20083                }
20084                self.generate_identifier(param)?;
20085            }
20086            self.write(" : ");
20087        } else {
20088            // Standard syntax: x -> expr or (x, y) -> expr
20089            if f.parameters.len() == 1 {
20090                self.generate_identifier(&f.parameters[0])?;
20091            } else {
20092                self.write("(");
20093                for (i, param) in f.parameters.iter().enumerate() {
20094                    if i > 0 {
20095                        self.write(", ");
20096                    }
20097                    self.generate_identifier(param)?;
20098                }
20099                self.write(")");
20100            }
20101            self.write(" -> ");
20102        }
20103        self.generate_expression(&f.body)
20104    }
20105
20106    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
20107        self.generate_identifier(&f.name)?;
20108        match f.separator {
20109            NamedArgSeparator::DArrow => self.write(" => "),
20110            NamedArgSeparator::ColonEq => self.write(" := "),
20111            NamedArgSeparator::Eq => self.write(" = "),
20112        }
20113        self.generate_expression(&f.value)
20114    }
20115
20116    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
20117        self.write_keyword(&f.prefix);
20118        self.write(" ");
20119        self.generate_expression(&f.this)
20120    }
20121
20122    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
20123        match f.style {
20124            ParameterStyle::Question => self.write("?"),
20125            ParameterStyle::Dollar => {
20126                self.write("$");
20127                if let Some(idx) = f.index {
20128                    self.write(&idx.to_string());
20129                } else if let Some(ref name) = f.name {
20130                    // Session variable like $x or $query_id
20131                    self.write(name);
20132                }
20133            }
20134            ParameterStyle::DollarBrace => {
20135                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20136                self.write("${");
20137                if let Some(ref name) = f.name {
20138                    self.write(name);
20139                }
20140                if let Some(ref expr) = f.expression {
20141                    self.write(":");
20142                    self.write(expr);
20143                }
20144                self.write("}");
20145            }
20146            ParameterStyle::Colon => {
20147                self.write(":");
20148                if let Some(idx) = f.index {
20149                    self.write(&idx.to_string());
20150                } else if let Some(ref name) = f.name {
20151                    self.write(name);
20152                }
20153            }
20154            ParameterStyle::At => {
20155                self.write("@");
20156                if let Some(ref name) = f.name {
20157                    if f.string_quoted {
20158                        self.write("'");
20159                        self.write(name);
20160                        self.write("'");
20161                    } else if f.quoted {
20162                        self.write("\"");
20163                        self.write(name);
20164                        self.write("\"");
20165                    } else {
20166                        self.write(name);
20167                    }
20168                }
20169            }
20170            ParameterStyle::DoubleAt => {
20171                self.write("@@");
20172                if let Some(ref name) = f.name {
20173                    self.write(name);
20174                }
20175            }
20176            ParameterStyle::DoubleDollar => {
20177                self.write("$$");
20178                if let Some(ref name) = f.name {
20179                    self.write(name);
20180                }
20181            }
20182            ParameterStyle::Percent => {
20183                if let Some(ref name) = f.name {
20184                    // %(name)s format
20185                    self.write("%(");
20186                    self.write(name);
20187                    self.write(")s");
20188                } else {
20189                    // %s format
20190                    self.write("%s");
20191                }
20192            }
20193            ParameterStyle::Brace => {
20194                // Spark/Databricks widget template variable: {name}
20195                // ClickHouse query parameter may include kind: {name: Type}
20196                self.write("{");
20197                if let Some(ref name) = f.name {
20198                    self.write(name);
20199                }
20200                if let Some(ref expr) = f.expression {
20201                    self.write(": ");
20202                    self.write(expr);
20203                }
20204                self.write("}");
20205            }
20206        }
20207        Ok(())
20208    }
20209
20210    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20211        self.write("?");
20212        if let Some(idx) = f.index {
20213            self.write(&idx.to_string());
20214        }
20215        Ok(())
20216    }
20217
20218    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20219        if f.is_block {
20220            self.write("/*");
20221            self.write(&f.text);
20222            self.write("*/");
20223        } else {
20224            self.write("--");
20225            self.write(&f.text);
20226        }
20227        Ok(())
20228    }
20229
20230    // Additional predicate generators
20231
20232    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20233        self.generate_expression(&f.this)?;
20234        if f.not {
20235            self.write_space();
20236            self.write_keyword("NOT");
20237        }
20238        self.write_space();
20239        self.write_keyword("SIMILAR TO");
20240        self.write_space();
20241        self.generate_expression(&f.pattern)?;
20242        if let Some(ref escape) = f.escape {
20243            self.write_space();
20244            self.write_keyword("ESCAPE");
20245            self.write_space();
20246            self.generate_expression(escape)?;
20247        }
20248        Ok(())
20249    }
20250
20251    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20252        self.generate_expression(&f.this)?;
20253        self.write_space();
20254        // Output comparison operator if present
20255        if let Some(op) = &f.op {
20256            match op {
20257                QuantifiedOp::Eq => self.write("="),
20258                QuantifiedOp::Neq => self.write("<>"),
20259                QuantifiedOp::Lt => self.write("<"),
20260                QuantifiedOp::Lte => self.write("<="),
20261                QuantifiedOp::Gt => self.write(">"),
20262                QuantifiedOp::Gte => self.write(">="),
20263            }
20264            self.write_space();
20265        }
20266        self.write_keyword(name);
20267
20268        // If the child is a Subquery, it provides its own parens — output with space
20269        if matches!(&f.subquery, Expression::Subquery(_)) {
20270            self.write_space();
20271            self.generate_expression(&f.subquery)?;
20272        } else {
20273            self.write("(");
20274
20275            let is_statement = matches!(
20276                &f.subquery,
20277                Expression::Select(_)
20278                    | Expression::Union(_)
20279                    | Expression::Intersect(_)
20280                    | Expression::Except(_)
20281            );
20282
20283            if self.config.pretty && is_statement {
20284                self.write_newline();
20285                self.indent_level += 1;
20286                self.write_indent();
20287            }
20288            self.generate_expression(&f.subquery)?;
20289            if self.config.pretty && is_statement {
20290                self.write_newline();
20291                self.indent_level -= 1;
20292                self.write_indent();
20293            }
20294            self.write(")");
20295        }
20296        Ok(())
20297    }
20298
20299    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20300        // Check if this is a simple binary form (this OVERLAPS expression)
20301        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20302            self.generate_expression(this)?;
20303            self.write_space();
20304            self.write_keyword("OVERLAPS");
20305            self.write_space();
20306            self.generate_expression(expr)?;
20307        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20308            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20309        {
20310            // Full ANSI form: (a, b) OVERLAPS (c, d)
20311            self.write("(");
20312            self.generate_expression(ls)?;
20313            self.write(", ");
20314            self.generate_expression(le)?;
20315            self.write(")");
20316            self.write_space();
20317            self.write_keyword("OVERLAPS");
20318            self.write_space();
20319            self.write("(");
20320            self.generate_expression(rs)?;
20321            self.write(", ");
20322            self.generate_expression(re)?;
20323            self.write(")");
20324        }
20325        Ok(())
20326    }
20327
20328    // Type conversion generators
20329
20330    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20331        use crate::dialects::DialectType;
20332
20333        // SingleStore uses !:> syntax for try cast
20334        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20335            self.generate_expression(&cast.this)?;
20336            self.write(" !:> ");
20337            self.generate_data_type(&cast.to)?;
20338            return Ok(());
20339        }
20340
20341        // Teradata uses TRYCAST (no underscore)
20342        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20343            self.write_keyword("TRYCAST");
20344            self.write("(");
20345            self.generate_expression(&cast.this)?;
20346            self.write_space();
20347            self.write_keyword("AS");
20348            self.write_space();
20349            self.generate_data_type(&cast.to)?;
20350            self.write(")");
20351            return Ok(());
20352        }
20353
20354        // Dialects without TRY_CAST: generate as regular CAST
20355        let keyword = if matches!(
20356            self.config.dialect,
20357            Some(DialectType::Hive)
20358                | Some(DialectType::MySQL)
20359                | Some(DialectType::SQLite)
20360                | Some(DialectType::Oracle)
20361                | Some(DialectType::ClickHouse)
20362                | Some(DialectType::Redshift)
20363                | Some(DialectType::PostgreSQL)
20364                | Some(DialectType::StarRocks)
20365                | Some(DialectType::Doris)
20366        ) {
20367            "CAST"
20368        } else {
20369            "TRY_CAST"
20370        };
20371
20372        self.write_keyword(keyword);
20373        self.write("(");
20374        self.generate_expression(&cast.this)?;
20375        self.write_space();
20376        self.write_keyword("AS");
20377        self.write_space();
20378        self.generate_data_type(&cast.to)?;
20379
20380        // Output FORMAT clause if present
20381        if let Some(format) = &cast.format {
20382            self.write_space();
20383            self.write_keyword("FORMAT");
20384            self.write_space();
20385            self.generate_expression(format)?;
20386        }
20387
20388        self.write(")");
20389        Ok(())
20390    }
20391
20392    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20393        self.write_keyword("SAFE_CAST");
20394        self.write("(");
20395        self.generate_expression(&cast.this)?;
20396        self.write_space();
20397        self.write_keyword("AS");
20398        self.write_space();
20399        self.generate_data_type(&cast.to)?;
20400
20401        // Output FORMAT clause if present
20402        if let Some(format) = &cast.format {
20403            self.write_space();
20404            self.write_keyword("FORMAT");
20405            self.write_space();
20406            self.generate_expression(format)?;
20407        }
20408
20409        self.write(")");
20410        Ok(())
20411    }
20412
20413    // Array/struct/map access generators
20414
20415    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20416        self.generate_expression(&s.this)?;
20417        self.write("[");
20418        self.generate_expression(&s.index)?;
20419        self.write("]");
20420        Ok(())
20421    }
20422
20423    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20424        self.generate_expression(&d.this)?;
20425        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20426        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20427        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20428            && matches!(
20429                &d.this,
20430                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20431            );
20432        if use_colon {
20433            self.write(":");
20434        } else {
20435            self.write(".");
20436        }
20437        self.generate_identifier(&d.field)
20438    }
20439
20440    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20441        self.generate_expression(&m.this)?;
20442        self.write(".");
20443        // Method names after a dot should not be quoted based on reserved keywords
20444        // Only quote if explicitly marked as quoted in the AST
20445        if m.method.quoted {
20446            let q = self.config.identifier_quote;
20447            self.write(&format!("{}{}{}", q, m.method.name, q));
20448        } else {
20449            self.write(&m.method.name);
20450        }
20451        self.write("(");
20452        for (i, arg) in m.args.iter().enumerate() {
20453            if i > 0 {
20454                self.write(", ");
20455            }
20456            self.generate_expression(arg)?;
20457        }
20458        self.write(")");
20459        Ok(())
20460    }
20461
20462    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20463        // Check if we need to wrap the inner expression in parentheses
20464        // JSON arrow expressions have lower precedence than array subscript
20465        let needs_parens = matches!(
20466            &s.this,
20467            Expression::JsonExtract(f) if f.arrow_syntax
20468        ) || matches!(
20469            &s.this,
20470            Expression::JsonExtractScalar(f) if f.arrow_syntax
20471        );
20472
20473        if needs_parens {
20474            self.write("(");
20475        }
20476        self.generate_expression(&s.this)?;
20477        if needs_parens {
20478            self.write(")");
20479        }
20480        self.write("[");
20481        if let Some(start) = &s.start {
20482            self.generate_expression(start)?;
20483        }
20484        self.write(":");
20485        if let Some(end) = &s.end {
20486            self.generate_expression(end)?;
20487        }
20488        self.write("]");
20489        Ok(())
20490    }
20491
20492    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20493        // Generate left expression, but skip trailing comments if they're already in left_comments
20494        // to avoid duplication (comments are captured as both expr.trailing_comments
20495        // and BinaryOp.left_comments during parsing)
20496        match &op.left {
20497            Expression::Column(col) => {
20498                // Generate column with trailing comments but skip them if they're
20499                // already captured in BinaryOp.left_comments to avoid duplication
20500                if let Some(table) = &col.table {
20501                    self.generate_identifier(table)?;
20502                    self.write(".");
20503                }
20504                self.generate_identifier(&col.name)?;
20505                // Oracle-style join marker (+)
20506                if col.join_mark && self.config.supports_column_join_marks {
20507                    self.write(" (+)");
20508                }
20509                // Output column trailing comments if they're not already in left_comments
20510                if op.left_comments.is_empty() {
20511                    for comment in &col.trailing_comments {
20512                        self.write_space();
20513                        self.write_formatted_comment(comment);
20514                    }
20515                }
20516            }
20517            Expression::Add(inner_op)
20518            | Expression::Sub(inner_op)
20519            | Expression::Mul(inner_op)
20520            | Expression::Div(inner_op)
20521            | Expression::Concat(inner_op) => {
20522                // Generate binary op without its trailing comments
20523                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20524                    Expression::Add(_) => "+",
20525                    Expression::Sub(_) => "-",
20526                    Expression::Mul(_) => "*",
20527                    Expression::Div(_) => "/",
20528                    Expression::Concat(_) => "||",
20529                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20530                })?;
20531            }
20532            _ => {
20533                self.generate_expression(&op.left)?;
20534            }
20535        }
20536        // Output comments after left operand
20537        for comment in &op.left_comments {
20538            self.write_space();
20539            self.write_formatted_comment(comment);
20540        }
20541        if self.config.pretty
20542            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20543            && (operator == "AND" || operator == "OR")
20544        {
20545            self.write_newline();
20546            self.write_indent();
20547            self.write_keyword(operator);
20548        } else {
20549            self.write_space();
20550            if operator.chars().all(|c| c.is_alphabetic()) {
20551                self.write_keyword(operator);
20552            } else {
20553                self.write(operator);
20554            }
20555        }
20556        // Output comments after operator (before right operand)
20557        for comment in &op.operator_comments {
20558            self.write_space();
20559            self.write_formatted_comment(comment);
20560        }
20561        self.write_space();
20562        self.generate_expression(&op.right)?;
20563        // Output trailing comments after right operand
20564        for comment in &op.trailing_comments {
20565            self.write_space();
20566            self.write_formatted_comment(comment);
20567        }
20568        Ok(())
20569    }
20570
20571    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
20572        let keyword = connector.keyword();
20573        let Some(terms) = self.flatten_connector_terms(op, connector) else {
20574            return self.generate_binary_op(op, keyword);
20575        };
20576
20577        self.generate_expression(terms[0])?;
20578        for term in terms.iter().skip(1) {
20579            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20580                self.write_newline();
20581                self.write_indent();
20582                self.write_keyword(keyword);
20583            } else {
20584                self.write_space();
20585                self.write_keyword(keyword);
20586            }
20587            self.write_space();
20588            self.generate_expression(term)?;
20589        }
20590
20591        Ok(())
20592    }
20593
20594    fn flatten_connector_terms<'a>(
20595        &self,
20596        root: &'a BinaryOp,
20597        connector: ConnectorOperator,
20598    ) -> Option<Vec<&'a Expression>> {
20599        if !root.left_comments.is_empty()
20600            || !root.operator_comments.is_empty()
20601            || !root.trailing_comments.is_empty()
20602        {
20603            return None;
20604        }
20605
20606        let mut terms = Vec::new();
20607        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
20608
20609        while let Some(expr) = stack.pop() {
20610            match (connector, expr) {
20611                (ConnectorOperator::And, Expression::And(inner))
20612                    if inner.left_comments.is_empty()
20613                        && inner.operator_comments.is_empty()
20614                        && inner.trailing_comments.is_empty() =>
20615                {
20616                    stack.push(&inner.right);
20617                    stack.push(&inner.left);
20618                }
20619                (ConnectorOperator::Or, Expression::Or(inner))
20620                    if inner.left_comments.is_empty()
20621                        && inner.operator_comments.is_empty()
20622                        && inner.trailing_comments.is_empty() =>
20623                {
20624                    stack.push(&inner.right);
20625                    stack.push(&inner.left);
20626                }
20627                _ => terms.push(expr),
20628            }
20629        }
20630
20631        if terms.len() > 1 {
20632            Some(terms)
20633        } else {
20634            None
20635        }
20636    }
20637
20638    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20639    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20640        self.generate_expression(&op.left)?;
20641        self.write_space();
20642        // Drill backtick-quotes ILIKE
20643        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20644            self.write("`ILIKE`");
20645        } else {
20646            self.write_keyword(operator);
20647        }
20648        if let Some(quantifier) = &op.quantifier {
20649            self.write_space();
20650            self.write_keyword(quantifier);
20651        }
20652        self.write_space();
20653        self.generate_expression(&op.right)?;
20654        if let Some(escape) = &op.escape {
20655            self.write_space();
20656            self.write_keyword("ESCAPE");
20657            self.write_space();
20658            self.generate_expression(escape)?;
20659        }
20660        Ok(())
20661    }
20662
20663    /// Generate null-safe equality
20664    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20665    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20666        use crate::dialects::DialectType;
20667        self.generate_expression(&op.left)?;
20668        self.write_space();
20669        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20670            self.write("<=>");
20671        } else {
20672            self.write_keyword("IS NOT DISTINCT FROM");
20673        }
20674        self.write_space();
20675        self.generate_expression(&op.right)?;
20676        Ok(())
20677    }
20678
20679    /// Generate IS DISTINCT FROM (null-safe inequality)
20680    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20681        self.generate_expression(&op.left)?;
20682        self.write_space();
20683        self.write_keyword("IS DISTINCT FROM");
20684        self.write_space();
20685        self.generate_expression(&op.right)?;
20686        Ok(())
20687    }
20688
20689    /// Generate binary op without trailing comments (used when nested inside another binary op)
20690    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20691        // Generate left expression, but skip trailing comments
20692        match &op.left {
20693            Expression::Column(col) => {
20694                if let Some(table) = &col.table {
20695                    self.generate_identifier(table)?;
20696                    self.write(".");
20697                }
20698                self.generate_identifier(&col.name)?;
20699                // Oracle-style join marker (+)
20700                if col.join_mark && self.config.supports_column_join_marks {
20701                    self.write(" (+)");
20702                }
20703            }
20704            Expression::Add(inner_op)
20705            | Expression::Sub(inner_op)
20706            | Expression::Mul(inner_op)
20707            | Expression::Div(inner_op)
20708            | Expression::Concat(inner_op) => {
20709                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20710                    Expression::Add(_) => "+",
20711                    Expression::Sub(_) => "-",
20712                    Expression::Mul(_) => "*",
20713                    Expression::Div(_) => "/",
20714                    Expression::Concat(_) => "||",
20715                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20716                })?;
20717            }
20718            _ => {
20719                self.generate_expression(&op.left)?;
20720            }
20721        }
20722        // Output left_comments
20723        for comment in &op.left_comments {
20724            self.write_space();
20725            self.write_formatted_comment(comment);
20726        }
20727        self.write_space();
20728        if operator.chars().all(|c| c.is_alphabetic()) {
20729            self.write_keyword(operator);
20730        } else {
20731            self.write(operator);
20732        }
20733        // Output operator_comments
20734        for comment in &op.operator_comments {
20735            self.write_space();
20736            self.write_formatted_comment(comment);
20737        }
20738        self.write_space();
20739        // Generate right expression, but skip trailing comments if it's a Column
20740        // (the parent's left_comments will output them)
20741        match &op.right {
20742            Expression::Column(col) => {
20743                if let Some(table) = &col.table {
20744                    self.generate_identifier(table)?;
20745                    self.write(".");
20746                }
20747                self.generate_identifier(&col.name)?;
20748                // Oracle-style join marker (+)
20749                if col.join_mark && self.config.supports_column_join_marks {
20750                    self.write(" (+)");
20751                }
20752            }
20753            _ => {
20754                self.generate_expression(&op.right)?;
20755            }
20756        }
20757        // Skip trailing_comments - parent will handle them via its left_comments
20758        Ok(())
20759    }
20760
20761    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20762        if operator.chars().all(|c| c.is_alphabetic()) {
20763            self.write_keyword(operator);
20764            self.write_space();
20765        } else {
20766            self.write(operator);
20767            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20768            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20769                self.write_space();
20770            }
20771        }
20772        self.generate_expression(&op.this)
20773    }
20774
20775    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20776        // Generic mode supports two styles for negated IN:
20777        // - Prefix: NOT a IN (...)
20778        // - Infix:  a NOT IN (...)
20779        let is_generic =
20780            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20781        let use_prefix_not =
20782            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20783        if use_prefix_not {
20784            self.write_keyword("NOT");
20785            self.write_space();
20786        }
20787        self.generate_expression(&in_expr.this)?;
20788        if in_expr.global {
20789            self.write_space();
20790            self.write_keyword("GLOBAL");
20791        }
20792        if in_expr.not && !use_prefix_not {
20793            self.write_space();
20794            self.write_keyword("NOT");
20795        }
20796        self.write_space();
20797        self.write_keyword("IN");
20798
20799        // BigQuery: IN UNNEST(expr)
20800        if let Some(unnest_expr) = &in_expr.unnest {
20801            self.write_space();
20802            self.write_keyword("UNNEST");
20803            self.write("(");
20804            self.generate_expression(unnest_expr)?;
20805            self.write(")");
20806            return Ok(());
20807        }
20808
20809        if let Some(query) = &in_expr.query {
20810            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20811            // vs a subquery (col IN (SELECT ...))
20812            let is_bare = in_expr.expressions.is_empty()
20813                && !matches!(
20814                    query,
20815                    Expression::Select(_)
20816                        | Expression::Union(_)
20817                        | Expression::Intersect(_)
20818                        | Expression::Except(_)
20819                        | Expression::Subquery(_)
20820                );
20821            if is_bare {
20822                // Bare identifier: no parentheses
20823                self.write_space();
20824                self.generate_expression(query)?;
20825            } else {
20826                // Subquery: with parentheses
20827                self.write(" (");
20828                let is_statement = matches!(
20829                    query,
20830                    Expression::Select(_)
20831                        | Expression::Union(_)
20832                        | Expression::Intersect(_)
20833                        | Expression::Except(_)
20834                        | Expression::Subquery(_)
20835                );
20836                if self.config.pretty && is_statement {
20837                    self.write_newline();
20838                    self.indent_level += 1;
20839                    self.write_indent();
20840                }
20841                self.generate_expression(query)?;
20842                if self.config.pretty && is_statement {
20843                    self.write_newline();
20844                    self.indent_level -= 1;
20845                    self.write_indent();
20846                }
20847                self.write(")");
20848            }
20849        } else {
20850            // DuckDB: IN without parentheses for single expression that is NOT a literal
20851            // (array/list membership like 'red' IN tbl.flags)
20852            // ClickHouse: IN without parentheses for single non-array expressions
20853            let is_duckdb = matches!(
20854                self.config.dialect,
20855                Some(crate::dialects::DialectType::DuckDB)
20856            );
20857            let is_clickhouse = matches!(
20858                self.config.dialect,
20859                Some(crate::dialects::DialectType::ClickHouse)
20860            );
20861            let single_expr = in_expr.expressions.len() == 1;
20862            if is_clickhouse && single_expr {
20863                if let Expression::Array(arr) = &in_expr.expressions[0] {
20864                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20865                    self.write(" (");
20866                    for (i, expr) in arr.expressions.iter().enumerate() {
20867                        if i > 0 {
20868                            self.write(", ");
20869                        }
20870                        self.generate_expression(expr)?;
20871                    }
20872                    self.write(")");
20873                } else {
20874                    self.write_space();
20875                    self.generate_expression(&in_expr.expressions[0])?;
20876                }
20877            } else {
20878                let is_bare_ref = single_expr
20879                    && matches!(
20880                        &in_expr.expressions[0],
20881                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20882                    );
20883                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20884                    // Bare field reference (no parens in source): IN identifier
20885                    // Also DuckDB: IN without parentheses for array/list membership
20886                    self.write_space();
20887                    self.generate_expression(&in_expr.expressions[0])?;
20888                } else {
20889                    // Standard IN (list)
20890                    self.write(" (");
20891                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20892                        if i > 0 {
20893                            self.write(", ");
20894                        }
20895                        self.generate_expression(expr)?;
20896                    }
20897                    self.write(")");
20898                }
20899            }
20900        }
20901
20902        Ok(())
20903    }
20904
20905    fn generate_between(&mut self, between: &Between) -> Result<()> {
20906        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20907        let use_prefix_not = between.not
20908            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20909        if use_prefix_not {
20910            self.write_keyword("NOT");
20911            self.write_space();
20912        }
20913        self.generate_expression(&between.this)?;
20914        if between.not && !use_prefix_not {
20915            self.write_space();
20916            self.write_keyword("NOT");
20917        }
20918        self.write_space();
20919        self.write_keyword("BETWEEN");
20920        // Emit SYMMETRIC/ASYMMETRIC if present
20921        if let Some(sym) = between.symmetric {
20922            if sym {
20923                self.write(" SYMMETRIC");
20924            } else {
20925                self.write(" ASYMMETRIC");
20926            }
20927        }
20928        self.write_space();
20929        self.generate_expression(&between.low)?;
20930        self.write_space();
20931        self.write_keyword("AND");
20932        self.write_space();
20933        self.generate_expression(&between.high)
20934    }
20935
20936    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20937        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20938        let use_prefix_not = is_null.not
20939            && (self.config.dialect.is_none()
20940                || self.config.dialect == Some(DialectType::Generic)
20941                || is_null.postfix_form);
20942        if use_prefix_not {
20943            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20944            self.write_keyword("NOT");
20945            self.write_space();
20946            self.generate_expression(&is_null.this)?;
20947            self.write_space();
20948            self.write_keyword("IS");
20949            self.write_space();
20950            self.write_keyword("NULL");
20951        } else {
20952            self.generate_expression(&is_null.this)?;
20953            self.write_space();
20954            self.write_keyword("IS");
20955            if is_null.not {
20956                self.write_space();
20957                self.write_keyword("NOT");
20958            }
20959            self.write_space();
20960            self.write_keyword("NULL");
20961        }
20962        Ok(())
20963    }
20964
20965    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20966        self.generate_expression(&is_true.this)?;
20967        self.write_space();
20968        self.write_keyword("IS");
20969        if is_true.not {
20970            self.write_space();
20971            self.write_keyword("NOT");
20972        }
20973        self.write_space();
20974        self.write_keyword("TRUE");
20975        Ok(())
20976    }
20977
20978    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20979        self.generate_expression(&is_false.this)?;
20980        self.write_space();
20981        self.write_keyword("IS");
20982        if is_false.not {
20983            self.write_space();
20984            self.write_keyword("NOT");
20985        }
20986        self.write_space();
20987        self.write_keyword("FALSE");
20988        Ok(())
20989    }
20990
20991    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20992        self.generate_expression(&is_json.this)?;
20993        self.write_space();
20994        self.write_keyword("IS");
20995        if is_json.negated {
20996            self.write_space();
20997            self.write_keyword("NOT");
20998        }
20999        self.write_space();
21000        self.write_keyword("JSON");
21001
21002        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
21003        if let Some(ref json_type) = is_json.json_type {
21004            self.write_space();
21005            self.write_keyword(json_type);
21006        }
21007
21008        // Output key uniqueness constraint if specified
21009        match &is_json.unique_keys {
21010            Some(JsonUniqueKeys::With) => {
21011                self.write_space();
21012                self.write_keyword("WITH UNIQUE KEYS");
21013            }
21014            Some(JsonUniqueKeys::Without) => {
21015                self.write_space();
21016                self.write_keyword("WITHOUT UNIQUE KEYS");
21017            }
21018            Some(JsonUniqueKeys::Shorthand) => {
21019                self.write_space();
21020                self.write_keyword("UNIQUE KEYS");
21021            }
21022            None => {}
21023        }
21024
21025        Ok(())
21026    }
21027
21028    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
21029        self.generate_expression(&is_expr.left)?;
21030        self.write_space();
21031        self.write_keyword("IS");
21032        self.write_space();
21033        self.generate_expression(&is_expr.right)
21034    }
21035
21036    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
21037        if exists.not {
21038            self.write_keyword("NOT");
21039            self.write_space();
21040        }
21041        self.write_keyword("EXISTS");
21042        self.write("(");
21043        let is_statement = matches!(
21044            &exists.this,
21045            Expression::Select(_)
21046                | Expression::Union(_)
21047                | Expression::Intersect(_)
21048                | Expression::Except(_)
21049        );
21050        if self.config.pretty && is_statement {
21051            self.write_newline();
21052            self.indent_level += 1;
21053            self.write_indent();
21054            self.generate_expression(&exists.this)?;
21055            self.write_newline();
21056            self.indent_level -= 1;
21057            self.write_indent();
21058            self.write(")");
21059        } else {
21060            self.generate_expression(&exists.this)?;
21061            self.write(")");
21062        }
21063        Ok(())
21064    }
21065
21066    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
21067        self.generate_expression(&op.left)?;
21068        self.write_space();
21069        self.write_keyword("MEMBER OF");
21070        self.write("(");
21071        self.generate_expression(&op.right)?;
21072        self.write(")");
21073        Ok(())
21074    }
21075
21076    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
21077        if subquery.lateral {
21078            self.write_keyword("LATERAL");
21079            self.write_space();
21080        }
21081
21082        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
21083        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
21084        // to carry the LIMIT modifier without adding more parens
21085        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
21086            matches!(
21087                &p.this,
21088                Expression::Select(_)
21089                    | Expression::Union(_)
21090                    | Expression::Intersect(_)
21091                    | Expression::Except(_)
21092                    | Expression::Subquery(_)
21093            )
21094        } else {
21095            false
21096        };
21097
21098        // Check if inner expression is a statement for pretty formatting
21099        let is_statement = matches!(
21100            &subquery.this,
21101            Expression::Select(_)
21102                | Expression::Union(_)
21103                | Expression::Intersect(_)
21104                | Expression::Except(_)
21105                | Expression::Merge(_)
21106        );
21107
21108        if !skip_outer_parens {
21109            self.write("(");
21110            if self.config.pretty && is_statement {
21111                self.write_newline();
21112                self.indent_level += 1;
21113                self.write_indent();
21114            }
21115        }
21116        self.generate_expression(&subquery.this)?;
21117
21118        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
21119        if subquery.modifiers_inside {
21120            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
21121            if let Some(order_by) = &subquery.order_by {
21122                self.write_space();
21123                self.write_keyword("ORDER BY");
21124                self.write_space();
21125                for (i, ord) in order_by.expressions.iter().enumerate() {
21126                    if i > 0 {
21127                        self.write(", ");
21128                    }
21129                    self.generate_ordered(ord)?;
21130                }
21131            }
21132
21133            if let Some(limit) = &subquery.limit {
21134                self.write_space();
21135                self.write_keyword("LIMIT");
21136                self.write_space();
21137                self.generate_expression(&limit.this)?;
21138                if limit.percent {
21139                    self.write_space();
21140                    self.write_keyword("PERCENT");
21141                }
21142            }
21143
21144            if let Some(offset) = &subquery.offset {
21145                self.write_space();
21146                self.write_keyword("OFFSET");
21147                self.write_space();
21148                self.generate_expression(&offset.this)?;
21149            }
21150        }
21151
21152        if !skip_outer_parens {
21153            if self.config.pretty && is_statement {
21154                self.write_newline();
21155                self.indent_level -= 1;
21156                self.write_indent();
21157            }
21158            self.write(")");
21159        }
21160
21161        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21162        if !subquery.modifiers_inside {
21163            if let Some(order_by) = &subquery.order_by {
21164                self.write_space();
21165                self.write_keyword("ORDER BY");
21166                self.write_space();
21167                for (i, ord) in order_by.expressions.iter().enumerate() {
21168                    if i > 0 {
21169                        self.write(", ");
21170                    }
21171                    self.generate_ordered(ord)?;
21172                }
21173            }
21174
21175            if let Some(limit) = &subquery.limit {
21176                self.write_space();
21177                self.write_keyword("LIMIT");
21178                self.write_space();
21179                self.generate_expression(&limit.this)?;
21180                if limit.percent {
21181                    self.write_space();
21182                    self.write_keyword("PERCENT");
21183                }
21184            }
21185
21186            if let Some(offset) = &subquery.offset {
21187                self.write_space();
21188                self.write_keyword("OFFSET");
21189                self.write_space();
21190                self.generate_expression(&offset.this)?;
21191            }
21192
21193            // Generate DISTRIBUTE BY (Hive/Spark)
21194            if let Some(distribute_by) = &subquery.distribute_by {
21195                self.write_space();
21196                self.write_keyword("DISTRIBUTE BY");
21197                self.write_space();
21198                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21199                    if i > 0 {
21200                        self.write(", ");
21201                    }
21202                    self.generate_expression(expr)?;
21203                }
21204            }
21205
21206            // Generate SORT BY (Hive/Spark)
21207            if let Some(sort_by) = &subquery.sort_by {
21208                self.write_space();
21209                self.write_keyword("SORT BY");
21210                self.write_space();
21211                for (i, ord) in sort_by.expressions.iter().enumerate() {
21212                    if i > 0 {
21213                        self.write(", ");
21214                    }
21215                    self.generate_ordered(ord)?;
21216                }
21217            }
21218
21219            // Generate CLUSTER BY (Hive/Spark)
21220            if let Some(cluster_by) = &subquery.cluster_by {
21221                self.write_space();
21222                self.write_keyword("CLUSTER BY");
21223                self.write_space();
21224                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21225                    if i > 0 {
21226                        self.write(", ");
21227                    }
21228                    self.generate_ordered(ord)?;
21229                }
21230            }
21231        }
21232
21233        if let Some(alias) = &subquery.alias {
21234            self.write_space();
21235            // Oracle doesn't use AS for subquery aliases
21236            let skip_as = matches!(
21237                self.config.dialect,
21238                Some(crate::dialects::DialectType::Oracle)
21239            );
21240            if !skip_as {
21241                self.write_keyword("AS");
21242                self.write_space();
21243            }
21244            self.generate_identifier(alias)?;
21245            if !subquery.column_aliases.is_empty() {
21246                self.write("(");
21247                for (i, col) in subquery.column_aliases.iter().enumerate() {
21248                    if i > 0 {
21249                        self.write(", ");
21250                    }
21251                    self.generate_identifier(col)?;
21252                }
21253                self.write(")");
21254            }
21255        }
21256        // Output trailing comments
21257        for comment in &subquery.trailing_comments {
21258            self.write(" ");
21259            self.write_formatted_comment(comment);
21260        }
21261        Ok(())
21262    }
21263
21264    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21265        // Generate WITH clause if present
21266        if let Some(ref with) = pivot.with {
21267            self.generate_with(with)?;
21268            self.write_space();
21269        }
21270
21271        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21272
21273        // Check for Redshift UNPIVOT in FROM clause:
21274        // UNPIVOT expr [AS val AT attr]
21275        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21276        let is_redshift_unpivot = pivot.unpivot
21277            && pivot.expressions.is_empty()
21278            && pivot.fields.is_empty()
21279            && pivot.using.is_empty()
21280            && pivot.into.is_none()
21281            && !matches!(&pivot.this, Expression::Null(_));
21282
21283        if is_redshift_unpivot {
21284            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21285            self.write_keyword("UNPIVOT");
21286            self.write_space();
21287            self.generate_expression(&pivot.this)?;
21288            // Alias - for Redshift it can be "val AT attr" format
21289            if let Some(alias) = &pivot.alias {
21290                self.write_space();
21291                self.write_keyword("AS");
21292                self.write_space();
21293                // The alias might contain " AT " for the attr part
21294                self.write(&alias.name);
21295            }
21296            return Ok(());
21297        }
21298
21299        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21300        let is_simplified = !pivot.using.is_empty()
21301            || pivot.into.is_some()
21302            || (pivot.fields.is_empty()
21303                && !pivot.expressions.is_empty()
21304                && !matches!(&pivot.this, Expression::Null(_)));
21305
21306        if is_simplified {
21307            // DuckDB simplified syntax:
21308            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21309            //   UNPIVOT table ON cols INTO NAME col VALUE col
21310            self.write_keyword(direction);
21311            self.write_space();
21312            self.generate_expression(&pivot.this)?;
21313
21314            if !pivot.expressions.is_empty() {
21315                self.write_space();
21316                self.write_keyword("ON");
21317                self.write_space();
21318                for (i, expr) in pivot.expressions.iter().enumerate() {
21319                    if i > 0 {
21320                        self.write(", ");
21321                    }
21322                    self.generate_expression(expr)?;
21323                }
21324            }
21325
21326            // INTO (for UNPIVOT)
21327            if let Some(into) = &pivot.into {
21328                self.write_space();
21329                self.write_keyword("INTO");
21330                self.write_space();
21331                self.generate_expression(into)?;
21332            }
21333
21334            // USING (for PIVOT)
21335            if !pivot.using.is_empty() {
21336                self.write_space();
21337                self.write_keyword("USING");
21338                self.write_space();
21339                for (i, expr) in pivot.using.iter().enumerate() {
21340                    if i > 0 {
21341                        self.write(", ");
21342                    }
21343                    self.generate_expression(expr)?;
21344                }
21345            }
21346
21347            // GROUP BY
21348            if let Some(group) = &pivot.group {
21349                self.write_space();
21350                self.generate_expression(group)?;
21351            }
21352        } else {
21353            // Standard syntax:
21354            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21355            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21356            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21357            if !matches!(&pivot.this, Expression::Null(_)) {
21358                self.generate_expression(&pivot.this)?;
21359                self.write_space();
21360            }
21361            self.write_keyword(direction);
21362            self.write("(");
21363
21364            // Aggregation expressions
21365            for (i, expr) in pivot.expressions.iter().enumerate() {
21366                if i > 0 {
21367                    self.write(", ");
21368                }
21369                self.generate_expression(expr)?;
21370            }
21371
21372            // FOR...IN fields
21373            if !pivot.fields.is_empty() {
21374                if !pivot.expressions.is_empty() {
21375                    self.write_space();
21376                }
21377                self.write_keyword("FOR");
21378                self.write_space();
21379                for (i, field) in pivot.fields.iter().enumerate() {
21380                    if i > 0 {
21381                        self.write_space();
21382                    }
21383                    // field is an In expression: column IN (values)
21384                    self.generate_expression(field)?;
21385                }
21386            }
21387
21388            // DEFAULT ON NULL
21389            if let Some(default_val) = &pivot.default_on_null {
21390                self.write_space();
21391                self.write_keyword("DEFAULT ON NULL");
21392                self.write(" (");
21393                self.generate_expression(default_val)?;
21394                self.write(")");
21395            }
21396
21397            // GROUP BY inside PIVOT parens
21398            if let Some(group) = &pivot.group {
21399                self.write_space();
21400                self.generate_expression(group)?;
21401            }
21402
21403            self.write(")");
21404        }
21405
21406        // Alias
21407        if let Some(alias) = &pivot.alias {
21408            self.write_space();
21409            self.write_keyword("AS");
21410            self.write_space();
21411            self.generate_identifier(alias)?;
21412        }
21413
21414        Ok(())
21415    }
21416
21417    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21418        self.generate_expression(&unpivot.this)?;
21419        self.write_space();
21420        self.write_keyword("UNPIVOT");
21421        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21422        if let Some(include) = unpivot.include_nulls {
21423            self.write_space();
21424            if include {
21425                self.write_keyword("INCLUDE NULLS");
21426            } else {
21427                self.write_keyword("EXCLUDE NULLS");
21428            }
21429            self.write_space();
21430        }
21431        self.write("(");
21432        if unpivot.value_column_parenthesized {
21433            self.write("(");
21434        }
21435        self.generate_identifier(&unpivot.value_column)?;
21436        // Output additional value columns if present
21437        for extra_col in &unpivot.extra_value_columns {
21438            self.write(", ");
21439            self.generate_identifier(extra_col)?;
21440        }
21441        if unpivot.value_column_parenthesized {
21442            self.write(")");
21443        }
21444        self.write_space();
21445        self.write_keyword("FOR");
21446        self.write_space();
21447        self.generate_identifier(&unpivot.name_column)?;
21448        self.write_space();
21449        self.write_keyword("IN");
21450        self.write(" (");
21451        for (i, col) in unpivot.columns.iter().enumerate() {
21452            if i > 0 {
21453                self.write(", ");
21454            }
21455            self.generate_expression(col)?;
21456        }
21457        self.write("))");
21458        if let Some(alias) = &unpivot.alias {
21459            self.write_space();
21460            self.write_keyword("AS");
21461            self.write_space();
21462            self.generate_identifier(alias)?;
21463        }
21464        Ok(())
21465    }
21466
21467    fn generate_values(&mut self, values: &Values) -> Result<()> {
21468        self.write_keyword("VALUES");
21469        for (i, row) in values.expressions.iter().enumerate() {
21470            if i > 0 {
21471                self.write(",");
21472            }
21473            self.write(" (");
21474            for (j, expr) in row.expressions.iter().enumerate() {
21475                if j > 0 {
21476                    self.write(", ");
21477                }
21478                self.generate_expression(expr)?;
21479            }
21480            self.write(")");
21481        }
21482        if let Some(alias) = &values.alias {
21483            self.write_space();
21484            self.write_keyword("AS");
21485            self.write_space();
21486            self.generate_identifier(alias)?;
21487            if !values.column_aliases.is_empty() {
21488                self.write("(");
21489                for (i, col) in values.column_aliases.iter().enumerate() {
21490                    if i > 0 {
21491                        self.write(", ");
21492                    }
21493                    self.generate_identifier(col)?;
21494                }
21495                self.write(")");
21496            }
21497        }
21498        Ok(())
21499    }
21500
21501    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21502        // Apply struct name inheritance for target dialects that need it
21503        let needs_inheritance = matches!(
21504            self.config.dialect,
21505            Some(DialectType::DuckDB)
21506                | Some(DialectType::Spark)
21507                | Some(DialectType::Databricks)
21508                | Some(DialectType::Hive)
21509                | Some(DialectType::Snowflake)
21510                | Some(DialectType::Presto)
21511                | Some(DialectType::Trino)
21512        );
21513        let propagated: Vec<Expression>;
21514        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21515            propagated = Self::inherit_struct_field_names(&arr.expressions);
21516            &propagated
21517        } else {
21518            &arr.expressions
21519        };
21520
21521        // Generic mode: ARRAY(1, 2, 3) with parentheses
21522        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21523        let use_parens =
21524            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21525        if !self.config.array_bracket_only {
21526            self.write_keyword("ARRAY");
21527        }
21528        if use_parens {
21529            self.write("(");
21530        } else {
21531            self.write("[");
21532        }
21533        for (i, expr) in expressions.iter().enumerate() {
21534            if i > 0 {
21535                self.write(", ");
21536            }
21537            self.generate_expression(expr)?;
21538        }
21539        if use_parens {
21540            self.write(")");
21541        } else {
21542            self.write("]");
21543        }
21544        Ok(())
21545    }
21546
21547    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21548        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21549        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21550        if tuple.expressions.len() == 2 {
21551            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21552                // First element is the function/expression, second is the TableAlias
21553                self.generate_expression(&tuple.expressions[0])?;
21554                self.write_space();
21555                self.write_keyword("AS");
21556                self.write_space();
21557                self.generate_expression(&tuple.expressions[1])?;
21558                return Ok(());
21559            }
21560        }
21561
21562        // In pretty mode, format long tuples with each element on a new line
21563        // Only expand if total width exceeds threshold
21564        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21565            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21566            for expr in &tuple.expressions {
21567                expr_strings.push(self.generate_to_string(expr)?);
21568            }
21569            self.too_wide(&expr_strings)
21570        } else {
21571            false
21572        };
21573
21574        if expand_tuple {
21575            self.write("(");
21576            self.write_newline();
21577            self.indent_level += 1;
21578            for (i, expr) in tuple.expressions.iter().enumerate() {
21579                if i > 0 {
21580                    self.write(",");
21581                    self.write_newline();
21582                }
21583                self.write_indent();
21584                self.generate_expression(expr)?;
21585            }
21586            self.indent_level -= 1;
21587            self.write_newline();
21588            self.write_indent();
21589            self.write(")");
21590        } else {
21591            self.write("(");
21592            for (i, expr) in tuple.expressions.iter().enumerate() {
21593                if i > 0 {
21594                    self.write(", ");
21595                }
21596                self.generate_expression(expr)?;
21597            }
21598            self.write(")");
21599        }
21600        Ok(())
21601    }
21602
21603    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21604        self.generate_expression(&pipe.this)?;
21605        self.write(" |> ");
21606        self.generate_expression(&pipe.expression)?;
21607        Ok(())
21608    }
21609
21610    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21611        self.generate_expression(&ordered.this)?;
21612        if ordered.desc {
21613            self.write_space();
21614            self.write_keyword("DESC");
21615        } else if ordered.explicit_asc {
21616            self.write_space();
21617            self.write_keyword("ASC");
21618        }
21619        if let Some(nulls_first) = ordered.nulls_first {
21620            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21621            // for the dialect. Different dialects have different NULL ordering defaults:
21622            //
21623            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21624            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21625            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21626            //
21627            // nulls_are_small (Spark, Hive, BigQuery, most others):
21628            //   - ASC: NULLS FIRST is default
21629            //   - DESC: NULLS LAST is default
21630            //
21631            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21632            //   - NULLS LAST is always the default regardless of sort direction
21633            let is_asc = !ordered.desc;
21634            let is_nulls_are_large = matches!(
21635                self.config.dialect,
21636                Some(DialectType::Oracle)
21637                    | Some(DialectType::PostgreSQL)
21638                    | Some(DialectType::Redshift)
21639                    | Some(DialectType::Snowflake)
21640            );
21641            let is_nulls_are_last = matches!(
21642                self.config.dialect,
21643                Some(DialectType::Dremio)
21644                    | Some(DialectType::DuckDB)
21645                    | Some(DialectType::Presto)
21646                    | Some(DialectType::Trino)
21647                    | Some(DialectType::Athena)
21648                    | Some(DialectType::ClickHouse)
21649                    | Some(DialectType::Drill)
21650                    | Some(DialectType::Exasol)
21651            );
21652
21653            // Check if the NULLS ordering matches the default for this dialect
21654            let is_default_nulls = if is_nulls_are_large {
21655                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21656                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21657            } else if is_nulls_are_last {
21658                // For nulls_are_last: NULLS LAST is always default
21659                !nulls_first
21660            } else {
21661                false
21662            };
21663
21664            if !is_default_nulls {
21665                self.write_space();
21666                self.write_keyword("NULLS");
21667                self.write_space();
21668                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21669            }
21670        }
21671        // WITH FILL clause (ClickHouse)
21672        if let Some(ref with_fill) = ordered.with_fill {
21673            self.write_space();
21674            self.generate_with_fill(with_fill)?;
21675        }
21676        Ok(())
21677    }
21678
21679    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21680    fn write_clickhouse_type(&mut self, type_str: &str) {
21681        if self.clickhouse_nullable_depth < 0 {
21682            // Map key context: don't wrap in Nullable
21683            self.write(type_str);
21684        } else {
21685            self.write(&format!("Nullable({})", type_str));
21686        }
21687    }
21688
21689    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21690        use crate::dialects::DialectType;
21691
21692        match dt {
21693            DataType::Boolean => {
21694                // Dialect-specific boolean type mappings
21695                match self.config.dialect {
21696                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21697                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21698                    Some(DialectType::Oracle) => {
21699                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21700                        self.write_keyword("NUMBER(1)")
21701                    }
21702                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21703                    _ => self.write_keyword("BOOLEAN"),
21704                }
21705            }
21706            DataType::TinyInt { length } => {
21707                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21708                // Dremio maps TINYINT to INT
21709                // ClickHouse maps TINYINT to Int8
21710                match self.config.dialect {
21711                    Some(DialectType::PostgreSQL)
21712                    | Some(DialectType::Redshift)
21713                    | Some(DialectType::Oracle)
21714                    | Some(DialectType::Exasol) => {
21715                        self.write_keyword("SMALLINT");
21716                    }
21717                    Some(DialectType::Teradata) => {
21718                        // Teradata uses BYTEINT for smallest integer
21719                        self.write_keyword("BYTEINT");
21720                    }
21721                    Some(DialectType::Dremio) => {
21722                        // Dremio maps TINYINT to INT
21723                        self.write_keyword("INT");
21724                    }
21725                    Some(DialectType::ClickHouse) => {
21726                        self.write_clickhouse_type("Int8");
21727                    }
21728                    _ => {
21729                        self.write_keyword("TINYINT");
21730                    }
21731                }
21732                if let Some(n) = length {
21733                    if !matches!(
21734                        self.config.dialect,
21735                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21736                    ) {
21737                        self.write(&format!("({})", n));
21738                    }
21739                }
21740            }
21741            DataType::SmallInt { length } => {
21742                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21743                match self.config.dialect {
21744                    Some(DialectType::Dremio) => {
21745                        self.write_keyword("INT");
21746                    }
21747                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21748                        self.write_keyword("INTEGER");
21749                    }
21750                    Some(DialectType::BigQuery) => {
21751                        self.write_keyword("INT64");
21752                    }
21753                    Some(DialectType::ClickHouse) => {
21754                        self.write_clickhouse_type("Int16");
21755                    }
21756                    _ => {
21757                        self.write_keyword("SMALLINT");
21758                        if let Some(n) = length {
21759                            self.write(&format!("({})", n));
21760                        }
21761                    }
21762                }
21763            }
21764            DataType::Int {
21765                length,
21766                integer_spelling,
21767            } => {
21768                // BigQuery uses INT64 for INT
21769                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21770                    self.write_keyword("INT64");
21771                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21772                    self.write_clickhouse_type("Int32");
21773                } else {
21774                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21775                    let use_integer = match self.config.dialect {
21776                        Some(DialectType::TSQL)
21777                        | Some(DialectType::Fabric)
21778                        | Some(DialectType::Presto)
21779                        | Some(DialectType::Trino)
21780                        | Some(DialectType::SQLite)
21781                        | Some(DialectType::Redshift) => true,
21782                        // Databricks preserves the original spelling
21783                        Some(DialectType::Databricks) => *integer_spelling,
21784                        _ => false,
21785                    };
21786                    if use_integer {
21787                        self.write_keyword("INTEGER");
21788                    } else {
21789                        self.write_keyword("INT");
21790                    }
21791                    if let Some(n) = length {
21792                        self.write(&format!("({})", n));
21793                    }
21794                }
21795            }
21796            DataType::BigInt { length } => {
21797                // Dialect-specific bigint type mappings
21798                match self.config.dialect {
21799                    Some(DialectType::Oracle) => {
21800                        // Oracle doesn't have BIGINT, uses INT
21801                        self.write_keyword("INT");
21802                    }
21803                    Some(DialectType::ClickHouse) => {
21804                        self.write_clickhouse_type("Int64");
21805                    }
21806                    _ => {
21807                        self.write_keyword("BIGINT");
21808                        if let Some(n) = length {
21809                            self.write(&format!("({})", n));
21810                        }
21811                    }
21812                }
21813            }
21814            DataType::Float {
21815                precision,
21816                scale,
21817                real_spelling,
21818            } => {
21819                // Dialect-specific float type mappings
21820                // If real_spelling is true, preserve REAL; otherwise use dialect default
21821                // Spark/Hive don't support REAL, always use FLOAT
21822                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21823                    self.write_clickhouse_type("Float32");
21824                } else if *real_spelling
21825                    && !matches!(
21826                        self.config.dialect,
21827                        Some(DialectType::Spark)
21828                            | Some(DialectType::Databricks)
21829                            | Some(DialectType::Hive)
21830                            | Some(DialectType::Snowflake)
21831                            | Some(DialectType::MySQL)
21832                            | Some(DialectType::BigQuery)
21833                    )
21834                {
21835                    self.write_keyword("REAL")
21836                } else {
21837                    match self.config.dialect {
21838                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21839                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21840                        _ => self.write_keyword("FLOAT"),
21841                    }
21842                }
21843                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21844                // Spark/Hive don't support FLOAT(precision)
21845                if !matches!(
21846                    self.config.dialect,
21847                    Some(DialectType::Spark)
21848                        | Some(DialectType::Databricks)
21849                        | Some(DialectType::Hive)
21850                        | Some(DialectType::Presto)
21851                        | Some(DialectType::Trino)
21852                ) {
21853                    if let Some(p) = precision {
21854                        self.write(&format!("({}", p));
21855                        if let Some(s) = scale {
21856                            self.write(&format!(", {})", s));
21857                        } else {
21858                            self.write(")");
21859                        }
21860                    }
21861                }
21862            }
21863            DataType::Double { precision, scale } => {
21864                // Dialect-specific double type mappings
21865                match self.config.dialect {
21866                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21867                        self.write_keyword("FLOAT")
21868                    } // SQL Server/Fabric FLOAT is double
21869                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21870                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21871                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21872                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21873                    Some(DialectType::PostgreSQL)
21874                    | Some(DialectType::Redshift)
21875                    | Some(DialectType::Teradata)
21876                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21877                    _ => self.write_keyword("DOUBLE"),
21878                }
21879                // MySQL supports DOUBLE(precision, scale)
21880                if let Some(p) = precision {
21881                    self.write(&format!("({}", p));
21882                    if let Some(s) = scale {
21883                        self.write(&format!(", {})", s));
21884                    } else {
21885                        self.write(")");
21886                    }
21887                }
21888            }
21889            DataType::Decimal { precision, scale } => {
21890                // Dialect-specific decimal type mappings
21891                match self.config.dialect {
21892                    Some(DialectType::ClickHouse) => {
21893                        self.write("Decimal");
21894                        if let Some(p) = precision {
21895                            self.write(&format!("({}", p));
21896                            if let Some(s) = scale {
21897                                self.write(&format!(", {}", s));
21898                            }
21899                            self.write(")");
21900                        }
21901                    }
21902                    Some(DialectType::Oracle) => {
21903                        // Oracle uses NUMBER instead of DECIMAL
21904                        self.write_keyword("NUMBER");
21905                        if let Some(p) = precision {
21906                            self.write(&format!("({}", p));
21907                            if let Some(s) = scale {
21908                                self.write(&format!(", {}", s));
21909                            }
21910                            self.write(")");
21911                        }
21912                    }
21913                    Some(DialectType::BigQuery) => {
21914                        // BigQuery uses NUMERIC instead of DECIMAL
21915                        self.write_keyword("NUMERIC");
21916                        if let Some(p) = precision {
21917                            self.write(&format!("({}", p));
21918                            if let Some(s) = scale {
21919                                self.write(&format!(", {}", s));
21920                            }
21921                            self.write(")");
21922                        }
21923                    }
21924                    _ => {
21925                        self.write_keyword("DECIMAL");
21926                        if let Some(p) = precision {
21927                            self.write(&format!("({}", p));
21928                            if let Some(s) = scale {
21929                                self.write(&format!(", {}", s));
21930                            }
21931                            self.write(")");
21932                        }
21933                    }
21934                }
21935            }
21936            DataType::Char { length } => {
21937                // Dialect-specific char type mappings
21938                match self.config.dialect {
21939                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21940                        // DuckDB/SQLite maps CHAR to TEXT
21941                        self.write_keyword("TEXT");
21942                    }
21943                    Some(DialectType::Hive)
21944                    | Some(DialectType::Spark)
21945                    | Some(DialectType::Databricks) => {
21946                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21947                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21948                        if length.is_some()
21949                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21950                        {
21951                            self.write_keyword("CHAR");
21952                            if let Some(n) = length {
21953                                self.write(&format!("({})", n));
21954                            }
21955                        } else {
21956                            self.write_keyword("STRING");
21957                        }
21958                    }
21959                    Some(DialectType::Dremio) => {
21960                        // Dremio maps CHAR to VARCHAR
21961                        self.write_keyword("VARCHAR");
21962                        if let Some(n) = length {
21963                            self.write(&format!("({})", n));
21964                        }
21965                    }
21966                    _ => {
21967                        self.write_keyword("CHAR");
21968                        if let Some(n) = length {
21969                            self.write(&format!("({})", n));
21970                        }
21971                    }
21972                }
21973            }
21974            DataType::VarChar {
21975                length,
21976                parenthesized_length,
21977            } => {
21978                // Dialect-specific varchar type mappings
21979                match self.config.dialect {
21980                    Some(DialectType::Oracle) => {
21981                        self.write_keyword("VARCHAR2");
21982                        if let Some(n) = length {
21983                            self.write(&format!("({})", n));
21984                        }
21985                    }
21986                    Some(DialectType::DuckDB) => {
21987                        // DuckDB maps VARCHAR to TEXT, preserving length
21988                        self.write_keyword("TEXT");
21989                        if let Some(n) = length {
21990                            self.write(&format!("({})", n));
21991                        }
21992                    }
21993                    Some(DialectType::SQLite) => {
21994                        // SQLite maps VARCHAR to TEXT, preserving length
21995                        self.write_keyword("TEXT");
21996                        if let Some(n) = length {
21997                            self.write(&format!("({})", n));
21998                        }
21999                    }
22000                    Some(DialectType::MySQL) if length.is_none() => {
22001                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
22002                        self.write_keyword("TEXT");
22003                    }
22004                    Some(DialectType::Hive)
22005                    | Some(DialectType::Spark)
22006                    | Some(DialectType::Databricks)
22007                        if length.is_none() =>
22008                    {
22009                        // Hive/Spark/Databricks: VARCHAR without length → STRING
22010                        self.write_keyword("STRING");
22011                    }
22012                    _ => {
22013                        self.write_keyword("VARCHAR");
22014                        if let Some(n) = length {
22015                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
22016                            if *parenthesized_length {
22017                                self.write(&format!("(({}))", n));
22018                            } else {
22019                                self.write(&format!("({})", n));
22020                            }
22021                        }
22022                    }
22023                }
22024            }
22025            DataType::Text => {
22026                // Dialect-specific text type mappings
22027                match self.config.dialect {
22028                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
22029                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22030                        self.write_keyword("VARCHAR(MAX)")
22031                    }
22032                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22033                    Some(DialectType::Snowflake)
22034                    | Some(DialectType::Dremio)
22035                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
22036                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
22037                    Some(DialectType::Presto)
22038                    | Some(DialectType::Trino)
22039                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22040                    Some(DialectType::Spark)
22041                    | Some(DialectType::Databricks)
22042                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22043                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
22044                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22045                        self.write_keyword("STRING")
22046                    }
22047                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22048                    _ => self.write_keyword("TEXT"),
22049                }
22050            }
22051            DataType::TextWithLength { length } => {
22052                // TEXT(n) - dialect-specific type with length
22053                match self.config.dialect {
22054                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
22055                    Some(DialectType::Hive)
22056                    | Some(DialectType::Spark)
22057                    | Some(DialectType::Databricks) => {
22058                        self.write(&format!("VARCHAR({})", length));
22059                    }
22060                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
22061                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
22062                    Some(DialectType::Snowflake)
22063                    | Some(DialectType::Presto)
22064                    | Some(DialectType::Trino)
22065                    | Some(DialectType::Athena)
22066                    | Some(DialectType::Drill)
22067                    | Some(DialectType::Dremio) => {
22068                        self.write(&format!("VARCHAR({})", length));
22069                    }
22070                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22071                        self.write(&format!("VARCHAR({})", length))
22072                    }
22073                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
22074                        self.write(&format!("STRING({})", length))
22075                    }
22076                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
22077                    _ => self.write(&format!("TEXT({})", length)),
22078                }
22079            }
22080            DataType::String { length } => {
22081                // STRING type with optional length (BigQuery STRING(n))
22082                match self.config.dialect {
22083                    Some(DialectType::ClickHouse) => {
22084                        // ClickHouse uses String with specific casing
22085                        self.write("String");
22086                        if let Some(n) = length {
22087                            self.write(&format!("({})", n));
22088                        }
22089                    }
22090                    Some(DialectType::BigQuery)
22091                    | Some(DialectType::Hive)
22092                    | Some(DialectType::Spark)
22093                    | Some(DialectType::Databricks)
22094                    | Some(DialectType::StarRocks)
22095                    | Some(DialectType::Doris) => {
22096                        self.write_keyword("STRING");
22097                        if let Some(n) = length {
22098                            self.write(&format!("({})", n));
22099                        }
22100                    }
22101                    Some(DialectType::PostgreSQL) => {
22102                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
22103                        if let Some(n) = length {
22104                            self.write_keyword("VARCHAR");
22105                            self.write(&format!("({})", n));
22106                        } else {
22107                            self.write_keyword("TEXT");
22108                        }
22109                    }
22110                    Some(DialectType::Redshift) => {
22111                        // Redshift: STRING -> VARCHAR(MAX)
22112                        if let Some(n) = length {
22113                            self.write_keyword("VARCHAR");
22114                            self.write(&format!("({})", n));
22115                        } else {
22116                            self.write_keyword("VARCHAR(MAX)");
22117                        }
22118                    }
22119                    Some(DialectType::MySQL) => {
22120                        // MySQL doesn't have STRING - use VARCHAR or TEXT
22121                        if let Some(n) = length {
22122                            self.write_keyword("VARCHAR");
22123                            self.write(&format!("({})", n));
22124                        } else {
22125                            self.write_keyword("TEXT");
22126                        }
22127                    }
22128                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22129                        // TSQL: STRING -> VARCHAR(MAX)
22130                        if let Some(n) = length {
22131                            self.write_keyword("VARCHAR");
22132                            self.write(&format!("({})", n));
22133                        } else {
22134                            self.write_keyword("VARCHAR(MAX)");
22135                        }
22136                    }
22137                    Some(DialectType::Oracle) => {
22138                        // Oracle: STRING -> CLOB
22139                        self.write_keyword("CLOB");
22140                    }
22141                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22142                        // DuckDB/Materialize uses TEXT for string types
22143                        self.write_keyword("TEXT");
22144                        if let Some(n) = length {
22145                            self.write(&format!("({})", n));
22146                        }
22147                    }
22148                    Some(DialectType::Presto)
22149                    | Some(DialectType::Trino)
22150                    | Some(DialectType::Drill)
22151                    | Some(DialectType::Dremio) => {
22152                        // Presto/Trino/Drill use VARCHAR for string types
22153                        self.write_keyword("VARCHAR");
22154                        if let Some(n) = length {
22155                            self.write(&format!("({})", n));
22156                        }
22157                    }
22158                    Some(DialectType::Snowflake) => {
22159                        // Snowflake: STRING stays as STRING (identity/DDL)
22160                        // CAST context STRING -> VARCHAR is handled in generate_cast
22161                        self.write_keyword("STRING");
22162                        if let Some(n) = length {
22163                            self.write(&format!("({})", n));
22164                        }
22165                    }
22166                    _ => {
22167                        // Default: output STRING with optional length
22168                        self.write_keyword("STRING");
22169                        if let Some(n) = length {
22170                            self.write(&format!("({})", n));
22171                        }
22172                    }
22173                }
22174            }
22175            DataType::Binary { length } => {
22176                // Dialect-specific binary type mappings
22177                match self.config.dialect {
22178                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22179                        self.write_keyword("BYTEA");
22180                        if let Some(n) = length {
22181                            self.write(&format!("({})", n));
22182                        }
22183                    }
22184                    Some(DialectType::Redshift) => {
22185                        self.write_keyword("VARBYTE");
22186                        if let Some(n) = length {
22187                            self.write(&format!("({})", n));
22188                        }
22189                    }
22190                    Some(DialectType::DuckDB)
22191                    | Some(DialectType::SQLite)
22192                    | Some(DialectType::Oracle) => {
22193                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22194                        self.write_keyword("BLOB");
22195                        if let Some(n) = length {
22196                            self.write(&format!("({})", n));
22197                        }
22198                    }
22199                    Some(DialectType::Presto)
22200                    | Some(DialectType::Trino)
22201                    | Some(DialectType::Athena)
22202                    | Some(DialectType::Drill)
22203                    | Some(DialectType::Dremio) => {
22204                        // These dialects map BINARY to VARBINARY
22205                        self.write_keyword("VARBINARY");
22206                        if let Some(n) = length {
22207                            self.write(&format!("({})", n));
22208                        }
22209                    }
22210                    Some(DialectType::ClickHouse) => {
22211                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22212                        if self.clickhouse_nullable_depth < 0 {
22213                            self.write("BINARY");
22214                        } else {
22215                            self.write("Nullable(BINARY");
22216                        }
22217                        if let Some(n) = length {
22218                            self.write(&format!("({})", n));
22219                        }
22220                        if self.clickhouse_nullable_depth >= 0 {
22221                            self.write(")");
22222                        }
22223                    }
22224                    _ => {
22225                        self.write_keyword("BINARY");
22226                        if let Some(n) = length {
22227                            self.write(&format!("({})", n));
22228                        }
22229                    }
22230                }
22231            }
22232            DataType::VarBinary { length } => {
22233                // Dialect-specific varbinary type mappings
22234                match self.config.dialect {
22235                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22236                        self.write_keyword("BYTEA");
22237                        if let Some(n) = length {
22238                            self.write(&format!("({})", n));
22239                        }
22240                    }
22241                    Some(DialectType::Redshift) => {
22242                        self.write_keyword("VARBYTE");
22243                        if let Some(n) = length {
22244                            self.write(&format!("({})", n));
22245                        }
22246                    }
22247                    Some(DialectType::DuckDB)
22248                    | Some(DialectType::SQLite)
22249                    | Some(DialectType::Oracle) => {
22250                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22251                        self.write_keyword("BLOB");
22252                        if let Some(n) = length {
22253                            self.write(&format!("({})", n));
22254                        }
22255                    }
22256                    Some(DialectType::Exasol) => {
22257                        // Exasol maps VARBINARY to VARCHAR
22258                        self.write_keyword("VARCHAR");
22259                    }
22260                    Some(DialectType::Spark)
22261                    | Some(DialectType::Hive)
22262                    | Some(DialectType::Databricks) => {
22263                        // Spark/Hive use BINARY instead of VARBINARY
22264                        self.write_keyword("BINARY");
22265                        if let Some(n) = length {
22266                            self.write(&format!("({})", n));
22267                        }
22268                    }
22269                    Some(DialectType::ClickHouse) => {
22270                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22271                        self.write_clickhouse_type("String");
22272                    }
22273                    _ => {
22274                        self.write_keyword("VARBINARY");
22275                        if let Some(n) = length {
22276                            self.write(&format!("({})", n));
22277                        }
22278                    }
22279                }
22280            }
22281            DataType::Blob => {
22282                // Dialect-specific blob type mappings
22283                match self.config.dialect {
22284                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22285                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22286                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22287                        self.write_keyword("VARBINARY")
22288                    }
22289                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22290                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22291                    Some(DialectType::Presto)
22292                    | Some(DialectType::Trino)
22293                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22294                    Some(DialectType::DuckDB) => {
22295                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22296                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22297                        self.write_keyword("VARBINARY");
22298                    }
22299                    Some(DialectType::Spark)
22300                    | Some(DialectType::Databricks)
22301                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22302                    Some(DialectType::ClickHouse) => {
22303                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22304                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22305                        // This matches Python sqlglot behavior.
22306                        self.write("Nullable(String)");
22307                    }
22308                    _ => self.write_keyword("BLOB"),
22309                }
22310            }
22311            DataType::Bit { length } => {
22312                // Dialect-specific bit type mappings
22313                match self.config.dialect {
22314                    Some(DialectType::Dremio)
22315                    | Some(DialectType::Spark)
22316                    | Some(DialectType::Databricks)
22317                    | Some(DialectType::Hive)
22318                    | Some(DialectType::Snowflake)
22319                    | Some(DialectType::BigQuery)
22320                    | Some(DialectType::Presto)
22321                    | Some(DialectType::Trino)
22322                    | Some(DialectType::ClickHouse)
22323                    | Some(DialectType::Redshift) => {
22324                        // These dialects don't support BIT type, use BOOLEAN
22325                        self.write_keyword("BOOLEAN");
22326                    }
22327                    _ => {
22328                        self.write_keyword("BIT");
22329                        if let Some(n) = length {
22330                            self.write(&format!("({})", n));
22331                        }
22332                    }
22333                }
22334            }
22335            DataType::VarBit { length } => {
22336                self.write_keyword("VARBIT");
22337                if let Some(n) = length {
22338                    self.write(&format!("({})", n));
22339                }
22340            }
22341            DataType::Date => self.write_keyword("DATE"),
22342            DataType::Time {
22343                precision,
22344                timezone,
22345            } => {
22346                if *timezone {
22347                    // Dialect-specific TIME WITH TIME ZONE output
22348                    match self.config.dialect {
22349                        Some(DialectType::DuckDB) => {
22350                            // DuckDB: TIMETZ (drops precision)
22351                            self.write_keyword("TIMETZ");
22352                        }
22353                        Some(DialectType::PostgreSQL) => {
22354                            // PostgreSQL: TIMETZ or TIMETZ(p)
22355                            self.write_keyword("TIMETZ");
22356                            if let Some(p) = precision {
22357                                self.write(&format!("({})", p));
22358                            }
22359                        }
22360                        _ => {
22361                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22362                            self.write_keyword("TIME");
22363                            if let Some(p) = precision {
22364                                self.write(&format!("({})", p));
22365                            }
22366                            self.write_keyword(" WITH TIME ZONE");
22367                        }
22368                    }
22369                } else {
22370                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22371                    if matches!(
22372                        self.config.dialect,
22373                        Some(DialectType::Spark)
22374                            | Some(DialectType::Databricks)
22375                            | Some(DialectType::Hive)
22376                    ) {
22377                        self.write_keyword("TIMESTAMP");
22378                    } else {
22379                        self.write_keyword("TIME");
22380                        if let Some(p) = precision {
22381                            self.write(&format!("({})", p));
22382                        }
22383                    }
22384                }
22385            }
22386            DataType::Timestamp {
22387                precision,
22388                timezone,
22389            } => {
22390                // Dialect-specific timestamp type mappings
22391                match self.config.dialect {
22392                    Some(DialectType::ClickHouse) => {
22393                        self.write("DateTime");
22394                        if let Some(p) = precision {
22395                            self.write(&format!("({})", p));
22396                        }
22397                    }
22398                    Some(DialectType::TSQL) => {
22399                        if *timezone {
22400                            self.write_keyword("DATETIMEOFFSET");
22401                        } else {
22402                            self.write_keyword("DATETIME2");
22403                        }
22404                        if let Some(p) = precision {
22405                            self.write(&format!("({})", p));
22406                        }
22407                    }
22408                    Some(DialectType::MySQL) => {
22409                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22410                        self.write_keyword("TIMESTAMP");
22411                        if let Some(p) = precision {
22412                            self.write(&format!("({})", p));
22413                        }
22414                    }
22415                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22416                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22417                        self.write_keyword("DATETIME");
22418                        if let Some(p) = precision {
22419                            self.write(&format!("({})", p));
22420                        }
22421                    }
22422                    Some(DialectType::BigQuery) => {
22423                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22424                        if *timezone {
22425                            self.write_keyword("TIMESTAMP");
22426                        } else {
22427                            self.write_keyword("DATETIME");
22428                        }
22429                    }
22430                    Some(DialectType::DuckDB) => {
22431                        // DuckDB: TIMESTAMPTZ shorthand
22432                        if *timezone {
22433                            self.write_keyword("TIMESTAMPTZ");
22434                        } else {
22435                            self.write_keyword("TIMESTAMP");
22436                            if let Some(p) = precision {
22437                                self.write(&format!("({})", p));
22438                            }
22439                        }
22440                    }
22441                    _ => {
22442                        if *timezone && !self.config.tz_to_with_time_zone {
22443                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22444                            self.write_keyword("TIMESTAMPTZ");
22445                            if let Some(p) = precision {
22446                                self.write(&format!("({})", p));
22447                            }
22448                        } else {
22449                            self.write_keyword("TIMESTAMP");
22450                            if let Some(p) = precision {
22451                                self.write(&format!("({})", p));
22452                            }
22453                            if *timezone {
22454                                self.write_space();
22455                                self.write_keyword("WITH TIME ZONE");
22456                            }
22457                        }
22458                    }
22459                }
22460            }
22461            DataType::Interval { unit, to } => {
22462                self.write_keyword("INTERVAL");
22463                if let Some(u) = unit {
22464                    self.write_space();
22465                    self.write_keyword(u);
22466                }
22467                // Handle range intervals like DAY TO HOUR
22468                if let Some(t) = to {
22469                    self.write_space();
22470                    self.write_keyword("TO");
22471                    self.write_space();
22472                    self.write_keyword(t);
22473                }
22474            }
22475            DataType::Json => {
22476                // Dialect-specific JSON type mappings
22477                match self.config.dialect {
22478                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22479                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22480                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22481                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22482                    _ => self.write_keyword("JSON"),
22483                }
22484            }
22485            DataType::JsonB => {
22486                // JSONB is PostgreSQL specific, but Doris also supports it
22487                match self.config.dialect {
22488                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22489                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22490                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22491                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22492                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22493                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22494                }
22495            }
22496            DataType::Uuid => {
22497                // Dialect-specific UUID type mappings
22498                match self.config.dialect {
22499                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22500                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22501                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22502                    Some(DialectType::BigQuery)
22503                    | Some(DialectType::Spark)
22504                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22505                    _ => self.write_keyword("UUID"),
22506                }
22507            }
22508            DataType::Array {
22509                element_type,
22510                dimension,
22511            } => {
22512                // Dialect-specific array syntax
22513                match self.config.dialect {
22514                    Some(DialectType::PostgreSQL)
22515                    | Some(DialectType::Redshift)
22516                    | Some(DialectType::DuckDB) => {
22517                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22518                        self.generate_data_type(element_type)?;
22519                        if let Some(dim) = dimension {
22520                            self.write(&format!("[{}]", dim));
22521                        } else {
22522                            self.write("[]");
22523                        }
22524                    }
22525                    Some(DialectType::BigQuery) => {
22526                        self.write_keyword("ARRAY<");
22527                        self.generate_data_type(element_type)?;
22528                        self.write(">");
22529                    }
22530                    Some(DialectType::Snowflake)
22531                    | Some(DialectType::Presto)
22532                    | Some(DialectType::Trino)
22533                    | Some(DialectType::ClickHouse) => {
22534                        // These dialects use Array(TYPE) parentheses syntax
22535                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22536                            self.write("Array(");
22537                        } else {
22538                            self.write_keyword("ARRAY(");
22539                        }
22540                        self.generate_data_type(element_type)?;
22541                        self.write(")");
22542                    }
22543                    Some(DialectType::TSQL)
22544                    | Some(DialectType::MySQL)
22545                    | Some(DialectType::Oracle) => {
22546                        // These dialects don't have native array types
22547                        // Fall back to JSON or use native workarounds
22548                        match self.config.dialect {
22549                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22550                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22551                            _ => self.write_keyword("JSON"),
22552                        }
22553                    }
22554                    _ => {
22555                        // Default: use angle bracket syntax (ARRAY<T>)
22556                        self.write_keyword("ARRAY<");
22557                        self.generate_data_type(element_type)?;
22558                        self.write(">");
22559                    }
22560                }
22561            }
22562            DataType::List { element_type } => {
22563                // Materialize: element_type LIST (postfix syntax)
22564                self.generate_data_type(element_type)?;
22565                self.write_keyword(" LIST");
22566            }
22567            DataType::Map {
22568                key_type,
22569                value_type,
22570            } => {
22571                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22572                match self.config.dialect {
22573                    Some(DialectType::Materialize) => {
22574                        // Materialize: MAP[key_type => value_type]
22575                        self.write_keyword("MAP[");
22576                        self.generate_data_type(key_type)?;
22577                        self.write(" => ");
22578                        self.generate_data_type(value_type)?;
22579                        self.write("]");
22580                    }
22581                    Some(DialectType::Snowflake)
22582                    | Some(DialectType::RisingWave)
22583                    | Some(DialectType::DuckDB)
22584                    | Some(DialectType::Presto)
22585                    | Some(DialectType::Trino)
22586                    | Some(DialectType::Athena) => {
22587                        self.write_keyword("MAP(");
22588                        self.generate_data_type(key_type)?;
22589                        self.write(", ");
22590                        self.generate_data_type(value_type)?;
22591                        self.write(")");
22592                    }
22593                    Some(DialectType::ClickHouse) => {
22594                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22595                        // Key types must NOT be wrapped in Nullable
22596                        self.write("Map(");
22597                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22598                        self.generate_data_type(key_type)?;
22599                        self.clickhouse_nullable_depth = 0;
22600                        self.write(", ");
22601                        self.generate_data_type(value_type)?;
22602                        self.write(")");
22603                    }
22604                    _ => {
22605                        self.write_keyword("MAP<");
22606                        self.generate_data_type(key_type)?;
22607                        self.write(", ");
22608                        self.generate_data_type(value_type)?;
22609                        self.write(">");
22610                    }
22611                }
22612            }
22613            DataType::Vector {
22614                element_type,
22615                dimension,
22616            } => {
22617                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22618                    // SingleStore format: VECTOR(dimension, type_alias)
22619                    self.write_keyword("VECTOR(");
22620                    if let Some(dim) = dimension {
22621                        self.write(&dim.to_string());
22622                    }
22623                    // Map type back to SingleStore alias
22624                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22625                        DataType::TinyInt { .. } => Some("I8"),
22626                        DataType::SmallInt { .. } => Some("I16"),
22627                        DataType::Int { .. } => Some("I32"),
22628                        DataType::BigInt { .. } => Some("I64"),
22629                        DataType::Float { .. } => Some("F32"),
22630                        DataType::Double { .. } => Some("F64"),
22631                        _ => None,
22632                    });
22633                    if let Some(alias) = type_alias {
22634                        if dimension.is_some() {
22635                            self.write(", ");
22636                        }
22637                        self.write(alias);
22638                    }
22639                    self.write(")");
22640                } else {
22641                    // Snowflake format: VECTOR(type, dimension)
22642                    self.write_keyword("VECTOR(");
22643                    if let Some(ref et) = element_type {
22644                        self.generate_data_type(et)?;
22645                        if dimension.is_some() {
22646                            self.write(", ");
22647                        }
22648                    }
22649                    if let Some(dim) = dimension {
22650                        self.write(&dim.to_string());
22651                    }
22652                    self.write(")");
22653                }
22654            }
22655            DataType::Object { fields, modifier } => {
22656                self.write_keyword("OBJECT(");
22657                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22658                    if i > 0 {
22659                        self.write(", ");
22660                    }
22661                    self.write(name);
22662                    self.write(" ");
22663                    self.generate_data_type(dt)?;
22664                    if *not_null {
22665                        self.write_keyword(" NOT NULL");
22666                    }
22667                }
22668                self.write(")");
22669                if let Some(mod_str) = modifier {
22670                    self.write(" ");
22671                    self.write_keyword(mod_str);
22672                }
22673            }
22674            DataType::Struct { fields, nested } => {
22675                // Dialect-specific struct type mappings
22676                match self.config.dialect {
22677                    Some(DialectType::Snowflake) => {
22678                        // Snowflake maps STRUCT to OBJECT
22679                        self.write_keyword("OBJECT(");
22680                        for (i, field) in fields.iter().enumerate() {
22681                            if i > 0 {
22682                                self.write(", ");
22683                            }
22684                            if !field.name.is_empty() {
22685                                self.write(&field.name);
22686                                self.write(" ");
22687                            }
22688                            self.generate_data_type(&field.data_type)?;
22689                        }
22690                        self.write(")");
22691                    }
22692                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22693                        // Presto/Trino use ROW(name TYPE, ...) syntax
22694                        self.write_keyword("ROW(");
22695                        for (i, field) in fields.iter().enumerate() {
22696                            if i > 0 {
22697                                self.write(", ");
22698                            }
22699                            if !field.name.is_empty() {
22700                                self.write(&field.name);
22701                                self.write(" ");
22702                            }
22703                            self.generate_data_type(&field.data_type)?;
22704                        }
22705                        self.write(")");
22706                    }
22707                    Some(DialectType::DuckDB) => {
22708                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22709                        self.write_keyword("STRUCT(");
22710                        for (i, field) in fields.iter().enumerate() {
22711                            if i > 0 {
22712                                self.write(", ");
22713                            }
22714                            if !field.name.is_empty() {
22715                                self.write(&field.name);
22716                                self.write(" ");
22717                            }
22718                            self.generate_data_type(&field.data_type)?;
22719                        }
22720                        self.write(")");
22721                    }
22722                    Some(DialectType::ClickHouse) => {
22723                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22724                        self.write("Tuple(");
22725                        for (i, field) in fields.iter().enumerate() {
22726                            if i > 0 {
22727                                self.write(", ");
22728                            }
22729                            if !field.name.is_empty() {
22730                                self.write(&field.name);
22731                                self.write(" ");
22732                            }
22733                            self.generate_data_type(&field.data_type)?;
22734                        }
22735                        self.write(")");
22736                    }
22737                    Some(DialectType::SingleStore) => {
22738                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22739                        self.write_keyword("RECORD(");
22740                        for (i, field) in fields.iter().enumerate() {
22741                            if i > 0 {
22742                                self.write(", ");
22743                            }
22744                            if !field.name.is_empty() {
22745                                self.write(&field.name);
22746                                self.write(" ");
22747                            }
22748                            self.generate_data_type(&field.data_type)?;
22749                        }
22750                        self.write(")");
22751                    }
22752                    _ => {
22753                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22754                        let force_angle_brackets = matches!(
22755                            self.config.dialect,
22756                            Some(DialectType::Hive)
22757                                | Some(DialectType::Spark)
22758                                | Some(DialectType::Databricks)
22759                        );
22760                        if *nested && !force_angle_brackets {
22761                            self.write_keyword("STRUCT(");
22762                            for (i, field) in fields.iter().enumerate() {
22763                                if i > 0 {
22764                                    self.write(", ");
22765                                }
22766                                if !field.name.is_empty() {
22767                                    self.write(&field.name);
22768                                    self.write(" ");
22769                                }
22770                                self.generate_data_type(&field.data_type)?;
22771                            }
22772                            self.write(")");
22773                        } else {
22774                            self.write_keyword("STRUCT<");
22775                            for (i, field) in fields.iter().enumerate() {
22776                                if i > 0 {
22777                                    self.write(", ");
22778                                }
22779                                if !field.name.is_empty() {
22780                                    // Named field: name TYPE (with configurable separator for Hive)
22781                                    self.write(&field.name);
22782                                    self.write(self.config.struct_field_sep);
22783                                }
22784                                // For anonymous fields, just output the type
22785                                self.generate_data_type(&field.data_type)?;
22786                                // Spark/Databricks: Output COMMENT clause if present
22787                                if let Some(comment) = &field.comment {
22788                                    self.write(" COMMENT '");
22789                                    self.write(comment);
22790                                    self.write("'");
22791                                }
22792                                // BigQuery: Output OPTIONS clause if present
22793                                if !field.options.is_empty() {
22794                                    self.write(" ");
22795                                    self.generate_options_clause(&field.options)?;
22796                                }
22797                            }
22798                            self.write(">");
22799                        }
22800                    }
22801                }
22802            }
22803            DataType::Enum {
22804                values,
22805                assignments,
22806            } => {
22807                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22808                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22809                if self.config.dialect == Some(DialectType::ClickHouse) {
22810                    self.write("Enum(");
22811                } else {
22812                    self.write_keyword("ENUM(");
22813                }
22814                for (i, val) in values.iter().enumerate() {
22815                    if i > 0 {
22816                        self.write(", ");
22817                    }
22818                    self.write("'");
22819                    self.write(val);
22820                    self.write("'");
22821                    if let Some(Some(assignment)) = assignments.get(i) {
22822                        self.write(" = ");
22823                        self.write(assignment);
22824                    }
22825                }
22826                self.write(")");
22827            }
22828            DataType::Set { values } => {
22829                // MySQL SET type: SET('a', 'b', 'c')
22830                self.write_keyword("SET(");
22831                for (i, val) in values.iter().enumerate() {
22832                    if i > 0 {
22833                        self.write(", ");
22834                    }
22835                    self.write("'");
22836                    self.write(val);
22837                    self.write("'");
22838                }
22839                self.write(")");
22840            }
22841            DataType::Union { fields } => {
22842                // DuckDB UNION type: UNION(num INT, str TEXT)
22843                self.write_keyword("UNION(");
22844                for (i, (name, dt)) in fields.iter().enumerate() {
22845                    if i > 0 {
22846                        self.write(", ");
22847                    }
22848                    if !name.is_empty() {
22849                        self.write(name);
22850                        self.write(" ");
22851                    }
22852                    self.generate_data_type(dt)?;
22853                }
22854                self.write(")");
22855            }
22856            DataType::Nullable { inner } => {
22857                // ClickHouse: Nullable(T), other dialects: just the inner type
22858                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22859                    self.write("Nullable(");
22860                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22861                    let saved_depth = self.clickhouse_nullable_depth;
22862                    self.clickhouse_nullable_depth = -1;
22863                    self.generate_data_type(inner)?;
22864                    self.clickhouse_nullable_depth = saved_depth;
22865                    self.write(")");
22866                } else {
22867                    // Map ClickHouse-specific custom type names to standard types
22868                    match inner.as_ref() {
22869                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22870                            self.generate_data_type(&DataType::Timestamp {
22871                                precision: None,
22872                                timezone: false,
22873                            })?;
22874                        }
22875                        _ => {
22876                            self.generate_data_type(inner)?;
22877                        }
22878                    }
22879                }
22880            }
22881            DataType::Custom { name } => {
22882                // Handle dialect-specific type transformations
22883                let name_upper = name.to_uppercase();
22884                match self.config.dialect {
22885                    Some(DialectType::ClickHouse) => {
22886                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22887                            (name_upper[..idx].to_string(), &name[idx..])
22888                        } else {
22889                            (name_upper.clone(), "")
22890                        };
22891                        let mapped = match base_upper.as_str() {
22892                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22893                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22894                            "DATETIME64" => "DateTime64",
22895                            "DATE32" => "Date32",
22896                            "INT" => "Int32",
22897                            "MEDIUMINT" => "Int32",
22898                            "INT8" => "Int8",
22899                            "INT16" => "Int16",
22900                            "INT32" => "Int32",
22901                            "INT64" => "Int64",
22902                            "INT128" => "Int128",
22903                            "INT256" => "Int256",
22904                            "UINT8" => "UInt8",
22905                            "UINT16" => "UInt16",
22906                            "UINT32" => "UInt32",
22907                            "UINT64" => "UInt64",
22908                            "UINT128" => "UInt128",
22909                            "UINT256" => "UInt256",
22910                            "FLOAT32" => "Float32",
22911                            "FLOAT64" => "Float64",
22912                            "DECIMAL32" => "Decimal32",
22913                            "DECIMAL64" => "Decimal64",
22914                            "DECIMAL128" => "Decimal128",
22915                            "DECIMAL256" => "Decimal256",
22916                            "ENUM" => "Enum",
22917                            "ENUM8" => "Enum8",
22918                            "ENUM16" => "Enum16",
22919                            "FIXEDSTRING" => "FixedString",
22920                            "NESTED" => "Nested",
22921                            "LOWCARDINALITY" => "LowCardinality",
22922                            "NULLABLE" => "Nullable",
22923                            "IPV4" => "IPv4",
22924                            "IPV6" => "IPv6",
22925                            "POINT" => "Point",
22926                            "RING" => "Ring",
22927                            "LINESTRING" => "LineString",
22928                            "MULTILINESTRING" => "MultiLineString",
22929                            "POLYGON" => "Polygon",
22930                            "MULTIPOLYGON" => "MultiPolygon",
22931                            "AGGREGATEFUNCTION" => "AggregateFunction",
22932                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22933                            "DYNAMIC" => "Dynamic",
22934                            _ => "",
22935                        };
22936                        if mapped.is_empty() {
22937                            self.write(name);
22938                        } else {
22939                            self.write(mapped);
22940                            self.write(suffix);
22941                        }
22942                    }
22943                    Some(DialectType::MySQL)
22944                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22945                    {
22946                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22947                        self.write_keyword("TIMESTAMP");
22948                    }
22949                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22950                        self.write_keyword("SQL_VARIANT");
22951                    }
22952                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22953                        self.write_keyword("DECIMAL(38, 5)");
22954                    }
22955                    Some(DialectType::Exasol) => {
22956                        // Exasol type mappings for custom types
22957                        match name_upper.as_str() {
22958                            // Binary types → VARCHAR
22959                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22960                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22961                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22962                            // Integer types
22963                            "MEDIUMINT" => self.write_keyword("INT"),
22964                            // Decimal types → DECIMAL
22965                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22966                                self.write_keyword("DECIMAL")
22967                            }
22968                            // Timestamp types
22969                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22970                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22971                            _ => self.write(name),
22972                        }
22973                    }
22974                    Some(DialectType::Dremio) => {
22975                        // Dremio type mappings for custom types
22976                        match name_upper.as_str() {
22977                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22978                            "ARRAY" => self.write_keyword("LIST"),
22979                            "NCHAR" => self.write_keyword("VARCHAR"),
22980                            _ => self.write(name),
22981                        }
22982                    }
22983                    // Map dialect-specific custom types to standard SQL types for other dialects
22984                    _ => {
22985                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22986                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22987                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22988                        } else {
22989                            (name_upper.clone(), None)
22990                        };
22991
22992                        match base_upper.as_str() {
22993                            "INT64"
22994                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22995                            {
22996                                self.write_keyword("BIGINT");
22997                            }
22998                            "FLOAT64"
22999                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23000                            {
23001                                self.write_keyword("DOUBLE");
23002                            }
23003                            "BOOL"
23004                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23005                            {
23006                                self.write_keyword("BOOLEAN");
23007                            }
23008                            "BYTES"
23009                                if matches!(
23010                                    self.config.dialect,
23011                                    Some(DialectType::Spark)
23012                                        | Some(DialectType::Hive)
23013                                        | Some(DialectType::Databricks)
23014                                ) =>
23015                            {
23016                                self.write_keyword("BINARY");
23017                            }
23018                            "BYTES"
23019                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23020                            {
23021                                self.write_keyword("VARBINARY");
23022                            }
23023                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
23024                            "DATETIME2" | "SMALLDATETIME"
23025                                if !matches!(
23026                                    self.config.dialect,
23027                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23028                                ) =>
23029                            {
23030                                // PostgreSQL preserves precision, others don't
23031                                if matches!(
23032                                    self.config.dialect,
23033                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23034                                ) {
23035                                    self.write_keyword("TIMESTAMP");
23036                                    if let Some(args) = _args_str {
23037                                        self.write(args);
23038                                    }
23039                                } else {
23040                                    self.write_keyword("TIMESTAMP");
23041                                }
23042                            }
23043                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
23044                            "DATETIMEOFFSET"
23045                                if !matches!(
23046                                    self.config.dialect,
23047                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23048                                ) =>
23049                            {
23050                                if matches!(
23051                                    self.config.dialect,
23052                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23053                                ) {
23054                                    self.write_keyword("TIMESTAMPTZ");
23055                                    if let Some(args) = _args_str {
23056                                        self.write(args);
23057                                    }
23058                                } else {
23059                                    self.write_keyword("TIMESTAMPTZ");
23060                                }
23061                            }
23062                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
23063                            "UNIQUEIDENTIFIER"
23064                                if !matches!(
23065                                    self.config.dialect,
23066                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23067                                ) =>
23068                            {
23069                                match self.config.dialect {
23070                                    Some(DialectType::Spark)
23071                                    | Some(DialectType::Databricks)
23072                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23073                                    _ => self.write_keyword("UUID"),
23074                                }
23075                            }
23076                            // TSQL BIT -> BOOLEAN for most dialects
23077                            "BIT"
23078                                if !matches!(
23079                                    self.config.dialect,
23080                                    Some(DialectType::TSQL)
23081                                        | Some(DialectType::Fabric)
23082                                        | Some(DialectType::PostgreSQL)
23083                                        | Some(DialectType::MySQL)
23084                                        | Some(DialectType::DuckDB)
23085                                ) =>
23086                            {
23087                                self.write_keyword("BOOLEAN");
23088                            }
23089                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
23090                            "NVARCHAR"
23091                                if !matches!(
23092                                    self.config.dialect,
23093                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23094                                ) =>
23095                            {
23096                                match self.config.dialect {
23097                                    Some(DialectType::Oracle) => {
23098                                        // Oracle: NVARCHAR -> NVARCHAR2
23099                                        self.write_keyword("NVARCHAR2");
23100                                        if let Some(args) = _args_str {
23101                                            self.write(args);
23102                                        }
23103                                    }
23104                                    Some(DialectType::BigQuery) => {
23105                                        // BigQuery: NVARCHAR -> STRING
23106                                        self.write_keyword("STRING");
23107                                    }
23108                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23109                                        self.write_keyword("TEXT");
23110                                        if let Some(args) = _args_str {
23111                                            self.write(args);
23112                                        }
23113                                    }
23114                                    Some(DialectType::Hive) => {
23115                                        // Hive: NVARCHAR -> STRING
23116                                        self.write_keyword("STRING");
23117                                    }
23118                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23119                                        if _args_str.is_some() {
23120                                            self.write_keyword("VARCHAR");
23121                                            self.write(_args_str.unwrap());
23122                                        } else {
23123                                            self.write_keyword("STRING");
23124                                        }
23125                                    }
23126                                    _ => {
23127                                        self.write_keyword("VARCHAR");
23128                                        if let Some(args) = _args_str {
23129                                            self.write(args);
23130                                        }
23131                                    }
23132                                }
23133                            }
23134                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23135                            "NCHAR"
23136                                if !matches!(
23137                                    self.config.dialect,
23138                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23139                                ) =>
23140                            {
23141                                match self.config.dialect {
23142                                    Some(DialectType::Oracle) => {
23143                                        // Oracle natively supports NCHAR
23144                                        self.write_keyword("NCHAR");
23145                                        if let Some(args) = _args_str {
23146                                            self.write(args);
23147                                        }
23148                                    }
23149                                    Some(DialectType::BigQuery) => {
23150                                        // BigQuery: NCHAR -> STRING
23151                                        self.write_keyword("STRING");
23152                                    }
23153                                    Some(DialectType::Hive) => {
23154                                        // Hive: NCHAR -> STRING
23155                                        self.write_keyword("STRING");
23156                                    }
23157                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23158                                        self.write_keyword("TEXT");
23159                                        if let Some(args) = _args_str {
23160                                            self.write(args);
23161                                        }
23162                                    }
23163                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23164                                        if _args_str.is_some() {
23165                                            self.write_keyword("CHAR");
23166                                            self.write(_args_str.unwrap());
23167                                        } else {
23168                                            self.write_keyword("STRING");
23169                                        }
23170                                    }
23171                                    _ => {
23172                                        self.write_keyword("CHAR");
23173                                        if let Some(args) = _args_str {
23174                                            self.write(args);
23175                                        }
23176                                    }
23177                                }
23178                            }
23179                            // MySQL text variant types -> map to appropriate target type
23180                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23181                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23182                                Some(DialectType::MySQL)
23183                                | Some(DialectType::SingleStore)
23184                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23185                                Some(DialectType::Spark)
23186                                | Some(DialectType::Databricks)
23187                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23188                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23189                                Some(DialectType::Presto)
23190                                | Some(DialectType::Trino)
23191                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23192                                Some(DialectType::Snowflake)
23193                                | Some(DialectType::Redshift)
23194                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23195                                _ => self.write_keyword("TEXT"),
23196                            },
23197                            // MySQL blob variant types -> map to appropriate target type
23198                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23199                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23200                                Some(DialectType::MySQL)
23201                                | Some(DialectType::SingleStore)
23202                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23203                                Some(DialectType::Spark)
23204                                | Some(DialectType::Databricks)
23205                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23206                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23207                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23208                                Some(DialectType::Presto)
23209                                | Some(DialectType::Trino)
23210                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23211                                Some(DialectType::Snowflake)
23212                                | Some(DialectType::Redshift)
23213                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23214                                _ => self.write_keyword("BLOB"),
23215                            },
23216                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23217                            "LONGVARCHAR" => match self.config.dialect {
23218                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23219                                _ => self.write_keyword("VARCHAR"),
23220                            },
23221                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23222                            "DATETIME" => {
23223                                match self.config.dialect {
23224                                    Some(DialectType::MySQL)
23225                                    | Some(DialectType::Doris)
23226                                    | Some(DialectType::StarRocks)
23227                                    | Some(DialectType::TSQL)
23228                                    | Some(DialectType::Fabric)
23229                                    | Some(DialectType::BigQuery)
23230                                    | Some(DialectType::SQLite)
23231                                    | Some(DialectType::Snowflake) => {
23232                                        self.write_keyword("DATETIME");
23233                                        if let Some(args) = _args_str {
23234                                            self.write(args);
23235                                        }
23236                                    }
23237                                    Some(_) => {
23238                                        // Only map to TIMESTAMP when we have a specific target dialect
23239                                        self.write_keyword("TIMESTAMP");
23240                                        if let Some(args) = _args_str {
23241                                            self.write(args);
23242                                        }
23243                                    }
23244                                    None => {
23245                                        // No dialect - preserve original
23246                                        self.write(name);
23247                                    }
23248                                }
23249                            }
23250                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23251                            "VARCHAR2"
23252                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23253                            {
23254                                match self.config.dialect {
23255                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23256                                        self.write_keyword("TEXT");
23257                                    }
23258                                    Some(DialectType::Hive)
23259                                    | Some(DialectType::Spark)
23260                                    | Some(DialectType::Databricks)
23261                                    | Some(DialectType::BigQuery)
23262                                    | Some(DialectType::ClickHouse)
23263                                    | Some(DialectType::StarRocks)
23264                                    | Some(DialectType::Doris) => {
23265                                        self.write_keyword("STRING");
23266                                    }
23267                                    _ => {
23268                                        self.write_keyword("VARCHAR");
23269                                        if let Some(args) = _args_str {
23270                                            self.write(args);
23271                                        }
23272                                    }
23273                                }
23274                            }
23275                            "NVARCHAR2"
23276                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23277                            {
23278                                match self.config.dialect {
23279                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23280                                        self.write_keyword("TEXT");
23281                                    }
23282                                    Some(DialectType::Hive)
23283                                    | Some(DialectType::Spark)
23284                                    | Some(DialectType::Databricks)
23285                                    | Some(DialectType::BigQuery)
23286                                    | Some(DialectType::ClickHouse)
23287                                    | Some(DialectType::StarRocks)
23288                                    | Some(DialectType::Doris) => {
23289                                        self.write_keyword("STRING");
23290                                    }
23291                                    _ => {
23292                                        self.write_keyword("VARCHAR");
23293                                        if let Some(args) = _args_str {
23294                                            self.write(args);
23295                                        }
23296                                    }
23297                                }
23298                            }
23299                            _ => self.write(name),
23300                        }
23301                    }
23302                }
23303            }
23304            DataType::Geometry { subtype, srid } => {
23305                // Dialect-specific geometry type mappings
23306                match self.config.dialect {
23307                    Some(DialectType::MySQL) => {
23308                        // MySQL uses POINT SRID 4326 syntax for specific types
23309                        if let Some(sub) = subtype {
23310                            self.write_keyword(sub);
23311                            if let Some(s) = srid {
23312                                self.write(" SRID ");
23313                                self.write(&s.to_string());
23314                            }
23315                        } else {
23316                            self.write_keyword("GEOMETRY");
23317                        }
23318                    }
23319                    Some(DialectType::BigQuery) => {
23320                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23321                        self.write_keyword("GEOGRAPHY");
23322                    }
23323                    Some(DialectType::Teradata) => {
23324                        // Teradata uses ST_GEOMETRY
23325                        self.write_keyword("ST_GEOMETRY");
23326                        if subtype.is_some() || srid.is_some() {
23327                            self.write("(");
23328                            if let Some(sub) = subtype {
23329                                self.write_keyword(sub);
23330                            }
23331                            if let Some(s) = srid {
23332                                if subtype.is_some() {
23333                                    self.write(", ");
23334                                }
23335                                self.write(&s.to_string());
23336                            }
23337                            self.write(")");
23338                        }
23339                    }
23340                    _ => {
23341                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23342                        self.write_keyword("GEOMETRY");
23343                        if subtype.is_some() || srid.is_some() {
23344                            self.write("(");
23345                            if let Some(sub) = subtype {
23346                                self.write_keyword(sub);
23347                            }
23348                            if let Some(s) = srid {
23349                                if subtype.is_some() {
23350                                    self.write(", ");
23351                                }
23352                                self.write(&s.to_string());
23353                            }
23354                            self.write(")");
23355                        }
23356                    }
23357                }
23358            }
23359            DataType::Geography { subtype, srid } => {
23360                // Dialect-specific geography type mappings
23361                match self.config.dialect {
23362                    Some(DialectType::MySQL) => {
23363                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23364                        if let Some(sub) = subtype {
23365                            self.write_keyword(sub);
23366                        } else {
23367                            self.write_keyword("GEOMETRY");
23368                        }
23369                        // Geography implies SRID 4326 (WGS84)
23370                        let effective_srid = srid.unwrap_or(4326);
23371                        self.write(" SRID ");
23372                        self.write(&effective_srid.to_string());
23373                    }
23374                    Some(DialectType::BigQuery) => {
23375                        // BigQuery uses simple GEOGRAPHY without parameters
23376                        self.write_keyword("GEOGRAPHY");
23377                    }
23378                    Some(DialectType::Snowflake) => {
23379                        // Snowflake uses GEOGRAPHY without parameters
23380                        self.write_keyword("GEOGRAPHY");
23381                    }
23382                    _ => {
23383                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23384                        self.write_keyword("GEOGRAPHY");
23385                        if subtype.is_some() || srid.is_some() {
23386                            self.write("(");
23387                            if let Some(sub) = subtype {
23388                                self.write_keyword(sub);
23389                            }
23390                            if let Some(s) = srid {
23391                                if subtype.is_some() {
23392                                    self.write(", ");
23393                                }
23394                                self.write(&s.to_string());
23395                            }
23396                            self.write(")");
23397                        }
23398                    }
23399                }
23400            }
23401            DataType::CharacterSet { name } => {
23402                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23403                self.write_keyword("CHAR CHARACTER SET ");
23404                self.write(name);
23405            }
23406            _ => self.write("UNKNOWN"),
23407        }
23408        Ok(())
23409    }
23410
23411    // === Helper methods ===
23412
23413    fn write(&mut self, s: &str) {
23414        self.output.push_str(s);
23415    }
23416
23417    fn write_space(&mut self) {
23418        self.output.push(' ');
23419    }
23420
23421    fn write_keyword(&mut self, keyword: &str) {
23422        if self.config.uppercase_keywords {
23423            self.output.push_str(keyword);
23424        } else {
23425            self.output.push_str(&keyword.to_lowercase());
23426        }
23427    }
23428
23429    /// Write a function name respecting the normalize_functions config setting
23430    fn write_func_name(&mut self, name: &str) {
23431        let normalized = self.normalize_func_name(name);
23432        self.output.push_str(&normalized);
23433    }
23434
23435    /// Convert strptime format string to Exasol format string
23436    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23437    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23438    fn convert_strptime_to_exasol_format(format: &str) -> String {
23439        let mut result = String::new();
23440        let chars: Vec<char> = format.chars().collect();
23441        let mut i = 0;
23442        while i < chars.len() {
23443            if chars[i] == '%' && i + 1 < chars.len() {
23444                let spec = chars[i + 1];
23445                let exasol_spec = match spec {
23446                    'Y' => "YYYY",
23447                    'y' => "YY",
23448                    'm' => "MM",
23449                    'd' => "DD",
23450                    'H' => "HH",
23451                    'M' => "MI",
23452                    'S' => "SS",
23453                    'a' => "DY",    // abbreviated weekday name
23454                    'A' => "DAY",   // full weekday name
23455                    'b' => "MON",   // abbreviated month name
23456                    'B' => "MONTH", // full month name
23457                    'I' => "H12",   // 12-hour format
23458                    'u' => "ID",    // ISO weekday (1-7)
23459                    'V' => "IW",    // ISO week number
23460                    'G' => "IYYY",  // ISO year
23461                    'W' => "UW",    // Week number (Monday as first day)
23462                    'U' => "UW",    // Week number (Sunday as first day)
23463                    'z' => "Z",     // timezone offset
23464                    _ => {
23465                        // Unknown specifier, keep as-is
23466                        result.push('%');
23467                        result.push(spec);
23468                        i += 2;
23469                        continue;
23470                    }
23471                };
23472                result.push_str(exasol_spec);
23473                i += 2;
23474            } else {
23475                result.push(chars[i]);
23476                i += 1;
23477            }
23478        }
23479        result
23480    }
23481
23482    /// Convert strptime format string to PostgreSQL/Redshift format string
23483    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23484    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23485    fn convert_strptime_to_postgres_format(format: &str) -> String {
23486        let mut result = String::new();
23487        let chars: Vec<char> = format.chars().collect();
23488        let mut i = 0;
23489        while i < chars.len() {
23490            if chars[i] == '%' && i + 1 < chars.len() {
23491                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23492                if chars[i + 1] == '-' && i + 2 < chars.len() {
23493                    let spec = chars[i + 2];
23494                    let pg_spec = match spec {
23495                        'd' => "FMDD",
23496                        'm' => "FMMM",
23497                        'H' => "FMHH24",
23498                        'M' => "FMMI",
23499                        'S' => "FMSS",
23500                        _ => {
23501                            result.push('%');
23502                            result.push('-');
23503                            result.push(spec);
23504                            i += 3;
23505                            continue;
23506                        }
23507                    };
23508                    result.push_str(pg_spec);
23509                    i += 3;
23510                    continue;
23511                }
23512                let spec = chars[i + 1];
23513                let pg_spec = match spec {
23514                    'Y' => "YYYY",
23515                    'y' => "YY",
23516                    'm' => "MM",
23517                    'd' => "DD",
23518                    'H' => "HH24",
23519                    'I' => "HH12",
23520                    'M' => "MI",
23521                    'S' => "SS",
23522                    'f' => "US",      // microseconds
23523                    'u' => "D",       // day of week (1=Monday)
23524                    'j' => "DDD",     // day of year
23525                    'z' => "OF",      // UTC offset
23526                    'Z' => "TZ",      // timezone name
23527                    'A' => "TMDay",   // full weekday name
23528                    'a' => "TMDy",    // abbreviated weekday name
23529                    'b' => "TMMon",   // abbreviated month name
23530                    'B' => "TMMonth", // full month name
23531                    'U' => "WW",      // week number
23532                    _ => {
23533                        // Unknown specifier, keep as-is
23534                        result.push('%');
23535                        result.push(spec);
23536                        i += 2;
23537                        continue;
23538                    }
23539                };
23540                result.push_str(pg_spec);
23541                i += 2;
23542            } else {
23543                result.push(chars[i]);
23544                i += 1;
23545            }
23546        }
23547        result
23548    }
23549
23550    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23551    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23552        if self.config.limit_only_literals {
23553            if let Some(value) = Self::try_evaluate_constant(expr) {
23554                self.write(&value.to_string());
23555                return Ok(());
23556            }
23557        }
23558        self.generate_expression(expr)
23559    }
23560
23561    /// Format a comment with proper spacing.
23562    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23563    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23564    fn write_formatted_comment(&mut self, comment: &str) {
23565        // Normalize all comments to block comment format /* ... */
23566        // This matches Python sqlglot behavior which always outputs block comments
23567        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23568            // Already block comment - extract inner content
23569            // Preserve internal whitespace, but ensure at least one space padding
23570            &comment[2..comment.len() - 2]
23571        } else if comment.starts_with("--") {
23572            // Line comment - extract content after --
23573            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23574            &comment[2..]
23575        } else {
23576            // Raw content (no delimiters)
23577            comment
23578        };
23579        // Skip empty comments (e.g., bare "--" with no content)
23580        if content.trim().is_empty() {
23581            return;
23582        }
23583        // Ensure at least one space after /* and before */
23584        self.output.push_str("/*");
23585        if !content.starts_with(' ') {
23586            self.output.push(' ');
23587        }
23588        self.output.push_str(content);
23589        if !content.ends_with(' ') {
23590            self.output.push(' ');
23591        }
23592        self.output.push_str("*/");
23593    }
23594
23595    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23596    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23597    fn escape_block_for_single_quote(&self, block: &str) -> String {
23598        let escape_backslash = matches!(
23599            self.config.dialect,
23600            Some(crate::dialects::DialectType::Snowflake)
23601        );
23602        let mut escaped = String::with_capacity(block.len() + 4);
23603        for ch in block.chars() {
23604            if ch == '\'' {
23605                escaped.push('\\');
23606                escaped.push('\'');
23607            } else if escape_backslash && ch == '\\' {
23608                escaped.push('\\');
23609                escaped.push('\\');
23610            } else {
23611                escaped.push(ch);
23612            }
23613        }
23614        escaped
23615    }
23616
23617    fn write_newline(&mut self) {
23618        self.output.push('\n');
23619    }
23620
23621    fn write_indent(&mut self) {
23622        for _ in 0..self.indent_level {
23623            self.output.push_str(&self.config.indent);
23624        }
23625    }
23626
23627    // === SQLGlot-style pretty printing helpers ===
23628
23629    /// Returns the separator string for pretty printing.
23630    /// Check if the total length of arguments exceeds max_text_width.
23631    /// Used for dynamic line breaking in expressions() formatting.
23632    fn too_wide(&self, args: &[String]) -> bool {
23633        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23634    }
23635
23636    /// Generate an expression to a string using a temporary non-pretty generator.
23637    /// Useful for width calculations before deciding on formatting.
23638    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23639        let config = GeneratorConfig {
23640            pretty: false,
23641            dialect: self.config.dialect,
23642            ..Default::default()
23643        };
23644        let mut gen = Generator::with_config(config);
23645        gen.generate_expression(expr)?;
23646        Ok(gen.output)
23647    }
23648
23649    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23650    /// In pretty mode: newline + indented keyword + newline + indented condition
23651    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23652        if self.config.pretty {
23653            self.write_newline();
23654            self.write_indent();
23655            self.write_keyword(keyword);
23656            self.write_newline();
23657            self.indent_level += 1;
23658            self.write_indent();
23659            self.generate_expression(condition)?;
23660            self.indent_level -= 1;
23661        } else {
23662            self.write_space();
23663            self.write_keyword(keyword);
23664            self.write_space();
23665            self.generate_expression(condition)?;
23666        }
23667        Ok(())
23668    }
23669
23670    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23671    /// In pretty mode: each expression on new line with indentation
23672    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23673        if exprs.is_empty() {
23674            return Ok(());
23675        }
23676
23677        if self.config.pretty {
23678            self.write_newline();
23679            self.write_indent();
23680            self.write_keyword(keyword);
23681            self.write_newline();
23682            self.indent_level += 1;
23683            for (i, expr) in exprs.iter().enumerate() {
23684                if i > 0 {
23685                    self.write(",");
23686                    self.write_newline();
23687                }
23688                self.write_indent();
23689                self.generate_expression(expr)?;
23690            }
23691            self.indent_level -= 1;
23692        } else {
23693            self.write_space();
23694            self.write_keyword(keyword);
23695            self.write_space();
23696            for (i, expr) in exprs.iter().enumerate() {
23697                if i > 0 {
23698                    self.write(", ");
23699                }
23700                self.generate_expression(expr)?;
23701            }
23702        }
23703        Ok(())
23704    }
23705
23706    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23707    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23708        if orderings.is_empty() {
23709            return Ok(());
23710        }
23711
23712        if self.config.pretty {
23713            self.write_newline();
23714            self.write_indent();
23715            self.write_keyword(keyword);
23716            self.write_newline();
23717            self.indent_level += 1;
23718            for (i, ordered) in orderings.iter().enumerate() {
23719                if i > 0 {
23720                    self.write(",");
23721                    self.write_newline();
23722                }
23723                self.write_indent();
23724                self.generate_ordered(ordered)?;
23725            }
23726            self.indent_level -= 1;
23727        } else {
23728            self.write_space();
23729            self.write_keyword(keyword);
23730            self.write_space();
23731            for (i, ordered) in orderings.iter().enumerate() {
23732                if i > 0 {
23733                    self.write(", ");
23734                }
23735                self.generate_ordered(ordered)?;
23736            }
23737        }
23738        Ok(())
23739    }
23740
23741    /// Writes WINDOW clause with named window definitions
23742    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23743        if windows.is_empty() {
23744            return Ok(());
23745        }
23746
23747        if self.config.pretty {
23748            self.write_newline();
23749            self.write_indent();
23750            self.write_keyword("WINDOW");
23751            self.write_newline();
23752            self.indent_level += 1;
23753            for (i, named_window) in windows.iter().enumerate() {
23754                if i > 0 {
23755                    self.write(",");
23756                    self.write_newline();
23757                }
23758                self.write_indent();
23759                self.generate_identifier(&named_window.name)?;
23760                self.write_space();
23761                self.write_keyword("AS");
23762                self.write(" (");
23763                self.generate_over(&named_window.spec)?;
23764                self.write(")");
23765            }
23766            self.indent_level -= 1;
23767        } else {
23768            self.write_space();
23769            self.write_keyword("WINDOW");
23770            self.write_space();
23771            for (i, named_window) in windows.iter().enumerate() {
23772                if i > 0 {
23773                    self.write(", ");
23774                }
23775                self.generate_identifier(&named_window.name)?;
23776                self.write_space();
23777                self.write_keyword("AS");
23778                self.write(" (");
23779                self.generate_over(&named_window.spec)?;
23780                self.write(")");
23781            }
23782        }
23783        Ok(())
23784    }
23785
23786    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23787    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23788        // AI_AGG(this, expression)
23789        self.write_keyword("AI_AGG");
23790        self.write("(");
23791        self.generate_expression(&e.this)?;
23792        self.write(", ");
23793        self.generate_expression(&e.expression)?;
23794        self.write(")");
23795        Ok(())
23796    }
23797
23798    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23799        // AI_CLASSIFY(input, [categories], [config])
23800        self.write_keyword("AI_CLASSIFY");
23801        self.write("(");
23802        self.generate_expression(&e.this)?;
23803        if let Some(categories) = &e.categories {
23804            self.write(", ");
23805            self.generate_expression(categories)?;
23806        }
23807        if let Some(config) = &e.config {
23808            self.write(", ");
23809            self.generate_expression(config)?;
23810        }
23811        self.write(")");
23812        Ok(())
23813    }
23814
23815    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23816        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23817        self.write_keyword("ADD");
23818        self.write_space();
23819        if e.exists {
23820            self.write_keyword("IF NOT EXISTS");
23821            self.write_space();
23822        }
23823        self.generate_expression(&e.this)?;
23824        if let Some(location) = &e.location {
23825            self.write_space();
23826            self.generate_expression(location)?;
23827        }
23828        Ok(())
23829    }
23830
23831    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23832        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23833        self.write_keyword("ALGORITHM");
23834        self.write("=");
23835        self.generate_expression(&e.this)?;
23836        Ok(())
23837    }
23838
23839    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23840        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23841        self.generate_expression(&e.this)?;
23842        self.write_space();
23843        self.write_keyword("AS");
23844        self.write(" (");
23845        for (i, expr) in e.expressions.iter().enumerate() {
23846            if i > 0 {
23847                self.write(", ");
23848            }
23849            self.generate_expression(expr)?;
23850        }
23851        self.write(")");
23852        Ok(())
23853    }
23854
23855    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23856        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23857        self.write_keyword("ALLOWED_VALUES");
23858        self.write_space();
23859        for (i, expr) in e.expressions.iter().enumerate() {
23860            if i > 0 {
23861                self.write(", ");
23862            }
23863            self.generate_expression(expr)?;
23864        }
23865        Ok(())
23866    }
23867
23868    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23869        // Python: complex logic based on dtype, default, comment, visible, etc.
23870        self.write_keyword("ALTER COLUMN");
23871        self.write_space();
23872        self.generate_expression(&e.this)?;
23873
23874        if let Some(dtype) = &e.dtype {
23875            self.write_space();
23876            self.write_keyword("SET DATA TYPE");
23877            self.write_space();
23878            self.generate_expression(dtype)?;
23879            if let Some(collate) = &e.collate {
23880                self.write_space();
23881                self.write_keyword("COLLATE");
23882                self.write_space();
23883                self.generate_expression(collate)?;
23884            }
23885            if let Some(using) = &e.using {
23886                self.write_space();
23887                self.write_keyword("USING");
23888                self.write_space();
23889                self.generate_expression(using)?;
23890            }
23891        } else if let Some(default) = &e.default {
23892            self.write_space();
23893            self.write_keyword("SET DEFAULT");
23894            self.write_space();
23895            self.generate_expression(default)?;
23896        } else if let Some(comment) = &e.comment {
23897            self.write_space();
23898            self.write_keyword("COMMENT");
23899            self.write_space();
23900            self.generate_expression(comment)?;
23901        } else if let Some(drop) = &e.drop {
23902            self.write_space();
23903            self.write_keyword("DROP");
23904            self.write_space();
23905            self.generate_expression(drop)?;
23906        } else if let Some(visible) = &e.visible {
23907            self.write_space();
23908            self.generate_expression(visible)?;
23909        } else if let Some(rename_to) = &e.rename_to {
23910            self.write_space();
23911            self.write_keyword("RENAME TO");
23912            self.write_space();
23913            self.generate_expression(rename_to)?;
23914        } else if let Some(allow_null) = &e.allow_null {
23915            self.write_space();
23916            self.generate_expression(allow_null)?;
23917        }
23918        Ok(())
23919    }
23920
23921    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23922        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23923        self.write_keyword("ALTER SESSION");
23924        self.write_space();
23925        if e.unset.is_some() {
23926            self.write_keyword("UNSET");
23927        } else {
23928            self.write_keyword("SET");
23929        }
23930        self.write_space();
23931        for (i, expr) in e.expressions.iter().enumerate() {
23932            if i > 0 {
23933                self.write(", ");
23934            }
23935            self.generate_expression(expr)?;
23936        }
23937        Ok(())
23938    }
23939
23940    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23941        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23942        self.write_keyword("SET");
23943
23944        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23945        if let Some(opt) = &e.option {
23946            self.write_space();
23947            self.generate_expression(opt)?;
23948        }
23949
23950        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23951        // Check if expressions look like property assignments
23952        if !e.expressions.is_empty() {
23953            // Check if this looks like property assignments (for SET PROPERTIES)
23954            let is_properties = e
23955                .expressions
23956                .iter()
23957                .any(|expr| matches!(expr, Expression::Eq(_)));
23958            if is_properties && e.option.is_none() {
23959                self.write_space();
23960                self.write_keyword("PROPERTIES");
23961            }
23962            self.write_space();
23963            for (i, expr) in e.expressions.iter().enumerate() {
23964                if i > 0 {
23965                    self.write(", ");
23966                }
23967                self.generate_expression(expr)?;
23968            }
23969        }
23970
23971        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23972        if let Some(file_format) = &e.file_format {
23973            self.write(" ");
23974            self.write_keyword("STAGE_FILE_FORMAT");
23975            self.write(" = (");
23976            self.generate_space_separated_properties(file_format)?;
23977            self.write(")");
23978        }
23979
23980        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23981        if let Some(copy_options) = &e.copy_options {
23982            self.write(" ");
23983            self.write_keyword("STAGE_COPY_OPTIONS");
23984            self.write(" = (");
23985            self.generate_space_separated_properties(copy_options)?;
23986            self.write(")");
23987        }
23988
23989        // Generate TAG ...
23990        if let Some(tag) = &e.tag {
23991            self.write(" ");
23992            self.write_keyword("TAG");
23993            self.write(" ");
23994            self.generate_expression(tag)?;
23995        }
23996
23997        Ok(())
23998    }
23999
24000    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
24001    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
24002        match expr {
24003            Expression::Tuple(t) => {
24004                for (i, prop) in t.expressions.iter().enumerate() {
24005                    if i > 0 {
24006                        self.write(" ");
24007                    }
24008                    self.generate_expression(prop)?;
24009                }
24010            }
24011            _ => {
24012                self.generate_expression(expr)?;
24013            }
24014        }
24015        Ok(())
24016    }
24017
24018    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
24019        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
24020        self.write_keyword("ALTER");
24021        if e.compound.is_some() {
24022            self.write_space();
24023            self.write_keyword("COMPOUND");
24024        }
24025        self.write_space();
24026        self.write_keyword("SORTKEY");
24027        self.write_space();
24028        if let Some(this) = &e.this {
24029            self.generate_expression(this)?;
24030        } else if !e.expressions.is_empty() {
24031            self.write("(");
24032            for (i, expr) in e.expressions.iter().enumerate() {
24033                if i > 0 {
24034                    self.write(", ");
24035                }
24036                self.generate_expression(expr)?;
24037            }
24038            self.write(")");
24039        }
24040        Ok(())
24041    }
24042
24043    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
24044        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
24045        self.write_keyword("ANALYZE");
24046        if !e.options.is_empty() {
24047            self.write_space();
24048            for (i, opt) in e.options.iter().enumerate() {
24049                if i > 0 {
24050                    self.write_space();
24051                }
24052                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
24053                if let Expression::Identifier(id) = opt {
24054                    self.write_keyword(&id.name);
24055                } else {
24056                    self.generate_expression(opt)?;
24057                }
24058            }
24059        }
24060        if let Some(kind) = &e.kind {
24061            self.write_space();
24062            self.write_keyword(kind);
24063        }
24064        if let Some(this) = &e.this {
24065            self.write_space();
24066            self.generate_expression(this)?;
24067        }
24068        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
24069        if !e.columns.is_empty() {
24070            self.write("(");
24071            for (i, col) in e.columns.iter().enumerate() {
24072                if i > 0 {
24073                    self.write(", ");
24074                }
24075                self.write(col);
24076            }
24077            self.write(")");
24078        }
24079        if let Some(partition) = &e.partition {
24080            self.write_space();
24081            self.generate_expression(partition)?;
24082        }
24083        if let Some(mode) = &e.mode {
24084            self.write_space();
24085            self.generate_expression(mode)?;
24086        }
24087        if let Some(expression) = &e.expression {
24088            self.write_space();
24089            self.generate_expression(expression)?;
24090        }
24091        if !e.properties.is_empty() {
24092            self.write_space();
24093            self.write_keyword(self.config.with_properties_prefix);
24094            self.write(" (");
24095            for (i, prop) in e.properties.iter().enumerate() {
24096                if i > 0 {
24097                    self.write(", ");
24098                }
24099                self.generate_expression(prop)?;
24100            }
24101            self.write(")");
24102        }
24103        Ok(())
24104    }
24105
24106    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
24107        // Python: return f"DELETE{kind} STATISTICS"
24108        self.write_keyword("DELETE");
24109        if let Some(kind) = &e.kind {
24110            self.write_space();
24111            self.write_keyword(kind);
24112        }
24113        self.write_space();
24114        self.write_keyword("STATISTICS");
24115        Ok(())
24116    }
24117
24118    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
24119        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
24120        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
24121        if let Expression::Identifier(id) = e.this.as_ref() {
24122            self.write_keyword(&id.name);
24123        } else {
24124            self.generate_expression(&e.this)?;
24125        }
24126        self.write_space();
24127        self.write_keyword("HISTOGRAM ON");
24128        self.write_space();
24129        for (i, expr) in e.expressions.iter().enumerate() {
24130            if i > 0 {
24131                self.write(", ");
24132            }
24133            self.generate_expression(expr)?;
24134        }
24135        if let Some(expression) = &e.expression {
24136            self.write_space();
24137            self.generate_expression(expression)?;
24138        }
24139        if let Some(update_options) = &e.update_options {
24140            self.write_space();
24141            self.generate_expression(update_options)?;
24142            self.write_space();
24143            self.write_keyword("UPDATE");
24144        }
24145        Ok(())
24146    }
24147
24148    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24149        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24150        self.write_keyword("LIST CHAINED ROWS");
24151        if let Some(expression) = &e.expression {
24152            self.write_space();
24153            self.write_keyword("INTO");
24154            self.write_space();
24155            self.generate_expression(expression)?;
24156        }
24157        Ok(())
24158    }
24159
24160    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24161        // Python: return f"SAMPLE {sample} {kind}"
24162        self.write_keyword("SAMPLE");
24163        self.write_space();
24164        if let Some(sample) = &e.sample {
24165            self.generate_expression(sample)?;
24166            self.write_space();
24167        }
24168        self.write_keyword(&e.kind);
24169        Ok(())
24170    }
24171
24172    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24173        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24174        self.write_keyword(&e.kind);
24175        if let Some(option) = &e.option {
24176            self.write_space();
24177            self.generate_expression(option)?;
24178        }
24179        self.write_space();
24180        self.write_keyword("STATISTICS");
24181        if let Some(this) = &e.this {
24182            self.write_space();
24183            self.generate_expression(this)?;
24184        }
24185        if !e.expressions.is_empty() {
24186            self.write_space();
24187            for (i, expr) in e.expressions.iter().enumerate() {
24188                if i > 0 {
24189                    self.write(", ");
24190                }
24191                self.generate_expression(expr)?;
24192            }
24193        }
24194        Ok(())
24195    }
24196
24197    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24198        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24199        self.write_keyword("VALIDATE");
24200        self.write_space();
24201        self.write_keyword(&e.kind);
24202        if let Some(this) = &e.this {
24203            self.write_space();
24204            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24205            if let Expression::Identifier(id) = this.as_ref() {
24206                self.write_keyword(&id.name);
24207            } else {
24208                self.generate_expression(this)?;
24209            }
24210        }
24211        if let Some(expression) = &e.expression {
24212            self.write_space();
24213            self.write_keyword("INTO");
24214            self.write_space();
24215            self.generate_expression(expression)?;
24216        }
24217        Ok(())
24218    }
24219
24220    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24221        // Python: return f"WITH {expressions}"
24222        self.write_keyword("WITH");
24223        self.write_space();
24224        for (i, expr) in e.expressions.iter().enumerate() {
24225            if i > 0 {
24226                self.write(", ");
24227            }
24228            self.generate_expression(expr)?;
24229        }
24230        Ok(())
24231    }
24232
24233    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24234        // Anonymous represents a generic function call: FUNC_NAME(args...)
24235        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24236        self.generate_expression(&e.this)?;
24237        self.write("(");
24238        for (i, arg) in e.expressions.iter().enumerate() {
24239            if i > 0 {
24240                self.write(", ");
24241            }
24242            self.generate_expression(arg)?;
24243        }
24244        self.write(")");
24245        Ok(())
24246    }
24247
24248    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24249        // Same as Anonymous but for aggregate functions
24250        self.generate_expression(&e.this)?;
24251        self.write("(");
24252        for (i, arg) in e.expressions.iter().enumerate() {
24253            if i > 0 {
24254                self.write(", ");
24255            }
24256            self.generate_expression(arg)?;
24257        }
24258        self.write(")");
24259        Ok(())
24260    }
24261
24262    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24263        // Python: return f"{this} APPLY({expr})"
24264        self.generate_expression(&e.this)?;
24265        self.write_space();
24266        self.write_keyword("APPLY");
24267        self.write("(");
24268        self.generate_expression(&e.expression)?;
24269        self.write(")");
24270        Ok(())
24271    }
24272
24273    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24274        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24275        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24276        self.write("(");
24277        self.generate_expression(&e.this)?;
24278        if let Some(percentile) = &e.percentile {
24279            self.write(", ");
24280            self.generate_expression(percentile)?;
24281        }
24282        self.write(")");
24283        Ok(())
24284    }
24285
24286    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24287        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24288        self.write_keyword("APPROX_QUANTILE");
24289        self.write("(");
24290        self.generate_expression(&e.this)?;
24291        if let Some(quantile) = &e.quantile {
24292            self.write(", ");
24293            self.generate_expression(quantile)?;
24294        }
24295        if let Some(accuracy) = &e.accuracy {
24296            self.write(", ");
24297            self.generate_expression(accuracy)?;
24298        }
24299        if let Some(weight) = &e.weight {
24300            self.write(", ");
24301            self.generate_expression(weight)?;
24302        }
24303        self.write(")");
24304        Ok(())
24305    }
24306
24307    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24308        // APPROX_QUANTILES(this, expression)
24309        self.write_keyword("APPROX_QUANTILES");
24310        self.write("(");
24311        self.generate_expression(&e.this)?;
24312        if let Some(expression) = &e.expression {
24313            self.write(", ");
24314            self.generate_expression(expression)?;
24315        }
24316        self.write(")");
24317        Ok(())
24318    }
24319
24320    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24321        // APPROX_TOP_K(this[, expression][, counters])
24322        self.write_keyword("APPROX_TOP_K");
24323        self.write("(");
24324        self.generate_expression(&e.this)?;
24325        if let Some(expression) = &e.expression {
24326            self.write(", ");
24327            self.generate_expression(expression)?;
24328        }
24329        if let Some(counters) = &e.counters {
24330            self.write(", ");
24331            self.generate_expression(counters)?;
24332        }
24333        self.write(")");
24334        Ok(())
24335    }
24336
24337    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24338        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24339        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24340        self.write("(");
24341        self.generate_expression(&e.this)?;
24342        if let Some(expression) = &e.expression {
24343            self.write(", ");
24344            self.generate_expression(expression)?;
24345        }
24346        self.write(")");
24347        Ok(())
24348    }
24349
24350    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24351        // APPROX_TOP_K_COMBINE(this[, expression])
24352        self.write_keyword("APPROX_TOP_K_COMBINE");
24353        self.write("(");
24354        self.generate_expression(&e.this)?;
24355        if let Some(expression) = &e.expression {
24356            self.write(", ");
24357            self.generate_expression(expression)?;
24358        }
24359        self.write(")");
24360        Ok(())
24361    }
24362
24363    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24364        // APPROX_TOP_K_ESTIMATE(this[, expression])
24365        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24366        self.write("(");
24367        self.generate_expression(&e.this)?;
24368        if let Some(expression) = &e.expression {
24369            self.write(", ");
24370            self.generate_expression(expression)?;
24371        }
24372        self.write(")");
24373        Ok(())
24374    }
24375
24376    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24377        // APPROX_TOP_SUM(this, expression[, count])
24378        self.write_keyword("APPROX_TOP_SUM");
24379        self.write("(");
24380        self.generate_expression(&e.this)?;
24381        self.write(", ");
24382        self.generate_expression(&e.expression)?;
24383        if let Some(count) = &e.count {
24384            self.write(", ");
24385            self.generate_expression(count)?;
24386        }
24387        self.write(")");
24388        Ok(())
24389    }
24390
24391    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24392        // ARG_MAX(this, expression[, count])
24393        self.write_keyword("ARG_MAX");
24394        self.write("(");
24395        self.generate_expression(&e.this)?;
24396        self.write(", ");
24397        self.generate_expression(&e.expression)?;
24398        if let Some(count) = &e.count {
24399            self.write(", ");
24400            self.generate_expression(count)?;
24401        }
24402        self.write(")");
24403        Ok(())
24404    }
24405
24406    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24407        // ARG_MIN(this, expression[, count])
24408        self.write_keyword("ARG_MIN");
24409        self.write("(");
24410        self.generate_expression(&e.this)?;
24411        self.write(", ");
24412        self.generate_expression(&e.expression)?;
24413        if let Some(count) = &e.count {
24414            self.write(", ");
24415            self.generate_expression(count)?;
24416        }
24417        self.write(")");
24418        Ok(())
24419    }
24420
24421    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24422        // ARRAY_ALL(this, expression)
24423        self.write_keyword("ARRAY_ALL");
24424        self.write("(");
24425        self.generate_expression(&e.this)?;
24426        self.write(", ");
24427        self.generate_expression(&e.expression)?;
24428        self.write(")");
24429        Ok(())
24430    }
24431
24432    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24433        // ARRAY_ANY(this, expression) - fallback implementation
24434        self.write_keyword("ARRAY_ANY");
24435        self.write("(");
24436        self.generate_expression(&e.this)?;
24437        self.write(", ");
24438        self.generate_expression(&e.expression)?;
24439        self.write(")");
24440        Ok(())
24441    }
24442
24443    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24444        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24445        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24446        self.write("(");
24447        for (i, expr) in e.expressions.iter().enumerate() {
24448            if i > 0 {
24449                self.write(", ");
24450            }
24451            self.generate_expression(expr)?;
24452        }
24453        self.write(")");
24454        Ok(())
24455    }
24456
24457    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24458        // ARRAY_SUM(this[, expression])
24459        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24460            self.write("arraySum");
24461        } else {
24462            self.write_keyword("ARRAY_SUM");
24463        }
24464        self.write("(");
24465        self.generate_expression(&e.this)?;
24466        if let Some(expression) = &e.expression {
24467            self.write(", ");
24468            self.generate_expression(expression)?;
24469        }
24470        self.write(")");
24471        Ok(())
24472    }
24473
24474    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24475        // Python: return f"{this} AT {index}"
24476        self.generate_expression(&e.this)?;
24477        self.write_space();
24478        self.write_keyword("AT");
24479        self.write_space();
24480        self.generate_expression(&e.expression)?;
24481        Ok(())
24482    }
24483
24484    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24485        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24486        self.write_keyword("ATTACH");
24487        if e.exists {
24488            self.write_space();
24489            self.write_keyword("IF NOT EXISTS");
24490        }
24491        self.write_space();
24492        self.generate_expression(&e.this)?;
24493        if !e.expressions.is_empty() {
24494            self.write(" (");
24495            for (i, expr) in e.expressions.iter().enumerate() {
24496                if i > 0 {
24497                    self.write(", ");
24498                }
24499                self.generate_expression(expr)?;
24500            }
24501            self.write(")");
24502        }
24503        Ok(())
24504    }
24505
24506    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24507        // AttachOption: this [expression]
24508        // Python sqlglot: no equals sign, just space-separated
24509        self.generate_expression(&e.this)?;
24510        if let Some(expression) = &e.expression {
24511            self.write_space();
24512            self.generate_expression(expression)?;
24513        }
24514        Ok(())
24515    }
24516
24517    /// Generate the auto_increment keyword and options for a column definition.
24518    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24519    /// GENERATED AS IDENTITY, etc.
24520    fn generate_auto_increment_keyword(
24521        &mut self,
24522        col: &crate::expressions::ColumnDef,
24523    ) -> Result<()> {
24524        use crate::dialects::DialectType;
24525        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24526            self.write_keyword("IDENTITY");
24527            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24528                self.write("(");
24529                if let Some(ref start) = col.auto_increment_start {
24530                    self.generate_expression(start)?;
24531                } else {
24532                    self.write("0");
24533                }
24534                self.write(", ");
24535                if let Some(ref inc) = col.auto_increment_increment {
24536                    self.generate_expression(inc)?;
24537                } else {
24538                    self.write("1");
24539                }
24540                self.write(")");
24541            }
24542        } else if matches!(
24543            self.config.dialect,
24544            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24545        ) {
24546            self.write_keyword("AUTOINCREMENT");
24547            if let Some(ref start) = col.auto_increment_start {
24548                self.write_space();
24549                self.write_keyword("START");
24550                self.write_space();
24551                self.generate_expression(start)?;
24552            }
24553            if let Some(ref inc) = col.auto_increment_increment {
24554                self.write_space();
24555                self.write_keyword("INCREMENT");
24556                self.write_space();
24557                self.generate_expression(inc)?;
24558            }
24559            if let Some(order) = col.auto_increment_order {
24560                self.write_space();
24561                if order {
24562                    self.write_keyword("ORDER");
24563                } else {
24564                    self.write_keyword("NOORDER");
24565                }
24566            }
24567        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24568            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24569            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24570                self.write(" (");
24571                let mut first = true;
24572                if let Some(ref start) = col.auto_increment_start {
24573                    self.write_keyword("START WITH");
24574                    self.write_space();
24575                    self.generate_expression(start)?;
24576                    first = false;
24577                }
24578                if let Some(ref inc) = col.auto_increment_increment {
24579                    if !first {
24580                        self.write_space();
24581                    }
24582                    self.write_keyword("INCREMENT BY");
24583                    self.write_space();
24584                    self.generate_expression(inc)?;
24585                }
24586                self.write(")");
24587            }
24588        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24589            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24590            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24591                self.write(" (");
24592                let mut first = true;
24593                if let Some(ref start) = col.auto_increment_start {
24594                    self.write_keyword("START WITH");
24595                    self.write_space();
24596                    self.generate_expression(start)?;
24597                    first = false;
24598                }
24599                if let Some(ref inc) = col.auto_increment_increment {
24600                    if !first {
24601                        self.write_space();
24602                    }
24603                    self.write_keyword("INCREMENT BY");
24604                    self.write_space();
24605                    self.generate_expression(inc)?;
24606                }
24607                self.write(")");
24608            }
24609        } else if matches!(
24610            self.config.dialect,
24611            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24612        ) {
24613            self.write_keyword("IDENTITY");
24614            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24615                self.write("(");
24616                if let Some(ref start) = col.auto_increment_start {
24617                    self.generate_expression(start)?;
24618                } else {
24619                    self.write("0");
24620                }
24621                self.write(", ");
24622                if let Some(ref inc) = col.auto_increment_increment {
24623                    self.generate_expression(inc)?;
24624                } else {
24625                    self.write("1");
24626                }
24627                self.write(")");
24628            }
24629        } else {
24630            self.write_keyword("AUTO_INCREMENT");
24631            if let Some(ref start) = col.auto_increment_start {
24632                self.write_space();
24633                self.write_keyword("START");
24634                self.write_space();
24635                self.generate_expression(start)?;
24636            }
24637            if let Some(ref inc) = col.auto_increment_increment {
24638                self.write_space();
24639                self.write_keyword("INCREMENT");
24640                self.write_space();
24641                self.generate_expression(inc)?;
24642            }
24643            if let Some(order) = col.auto_increment_order {
24644                self.write_space();
24645                if order {
24646                    self.write_keyword("ORDER");
24647                } else {
24648                    self.write_keyword("NOORDER");
24649                }
24650            }
24651        }
24652        Ok(())
24653    }
24654
24655    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24656        // AUTO_INCREMENT=value
24657        self.write_keyword("AUTO_INCREMENT");
24658        self.write("=");
24659        self.generate_expression(&e.this)?;
24660        Ok(())
24661    }
24662
24663    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24664        // AUTO_REFRESH=value
24665        self.write_keyword("AUTO_REFRESH");
24666        self.write("=");
24667        self.generate_expression(&e.this)?;
24668        Ok(())
24669    }
24670
24671    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24672        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24673        self.write_keyword("BACKUP");
24674        self.write_space();
24675        self.generate_expression(&e.this)?;
24676        Ok(())
24677    }
24678
24679    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24680        // BASE64_DECODE_BINARY(this[, alphabet])
24681        self.write_keyword("BASE64_DECODE_BINARY");
24682        self.write("(");
24683        self.generate_expression(&e.this)?;
24684        if let Some(alphabet) = &e.alphabet {
24685            self.write(", ");
24686            self.generate_expression(alphabet)?;
24687        }
24688        self.write(")");
24689        Ok(())
24690    }
24691
24692    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24693        // BASE64_DECODE_STRING(this[, alphabet])
24694        self.write_keyword("BASE64_DECODE_STRING");
24695        self.write("(");
24696        self.generate_expression(&e.this)?;
24697        if let Some(alphabet) = &e.alphabet {
24698            self.write(", ");
24699            self.generate_expression(alphabet)?;
24700        }
24701        self.write(")");
24702        Ok(())
24703    }
24704
24705    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24706        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24707        self.write_keyword("BASE64_ENCODE");
24708        self.write("(");
24709        self.generate_expression(&e.this)?;
24710        if let Some(max_line_length) = &e.max_line_length {
24711            self.write(", ");
24712            self.generate_expression(max_line_length)?;
24713        }
24714        if let Some(alphabet) = &e.alphabet {
24715            self.write(", ");
24716            self.generate_expression(alphabet)?;
24717        }
24718        self.write(")");
24719        Ok(())
24720    }
24721
24722    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24723        // BLOCKCOMPRESSION=... (complex Teradata property)
24724        self.write_keyword("BLOCKCOMPRESSION");
24725        self.write("=");
24726        if let Some(autotemp) = &e.autotemp {
24727            self.write_keyword("AUTOTEMP");
24728            self.write("(");
24729            self.generate_expression(autotemp)?;
24730            self.write(")");
24731        }
24732        if let Some(always) = &e.always {
24733            self.generate_expression(always)?;
24734        }
24735        if let Some(default) = &e.default {
24736            self.generate_expression(default)?;
24737        }
24738        if let Some(manual) = &e.manual {
24739            self.generate_expression(manual)?;
24740        }
24741        if let Some(never) = &e.never {
24742            self.generate_expression(never)?;
24743        }
24744        Ok(())
24745    }
24746
24747    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24748        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24749        self.write("((");
24750        self.generate_expression(&e.this)?;
24751        self.write(") ");
24752        self.write_keyword("AND");
24753        self.write(" (");
24754        self.generate_expression(&e.expression)?;
24755        self.write("))");
24756        Ok(())
24757    }
24758
24759    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24760        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24761        self.write("((");
24762        self.generate_expression(&e.this)?;
24763        self.write(") ");
24764        self.write_keyword("OR");
24765        self.write(" (");
24766        self.generate_expression(&e.expression)?;
24767        self.write("))");
24768        Ok(())
24769    }
24770
24771    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24772        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24773        self.write_keyword("BUILD");
24774        self.write_space();
24775        self.generate_expression(&e.this)?;
24776        Ok(())
24777    }
24778
24779    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24780        // Byte string literal like B'...' or X'...'
24781        self.generate_expression(&e.this)?;
24782        Ok(())
24783    }
24784
24785    fn generate_case_specific_column_constraint(
24786        &mut self,
24787        e: &CaseSpecificColumnConstraint,
24788    ) -> Result<()> {
24789        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24790        if e.not_.is_some() {
24791            self.write_keyword("NOT");
24792            self.write_space();
24793        }
24794        self.write_keyword("CASESPECIFIC");
24795        Ok(())
24796    }
24797
24798    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24799        // Cast to string type (dialect-specific)
24800        self.write_keyword("CAST");
24801        self.write("(");
24802        self.generate_expression(&e.this)?;
24803        if self.config.dialect == Some(DialectType::ClickHouse) {
24804            // ClickHouse: CAST(expr, 'type_string')
24805            self.write(", ");
24806        } else {
24807            self.write_space();
24808            self.write_keyword("AS");
24809            self.write_space();
24810        }
24811        if let Some(to) = &e.to {
24812            self.generate_expression(to)?;
24813        }
24814        self.write(")");
24815        Ok(())
24816    }
24817
24818    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24819        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24820        // Python: f"CHANGES ({information}){at_before}{end}"
24821        self.write_keyword("CHANGES");
24822        self.write(" (");
24823        if let Some(information) = &e.information {
24824            self.write_keyword("INFORMATION");
24825            self.write(" => ");
24826            self.generate_expression(information)?;
24827        }
24828        self.write(")");
24829        // at_before and end are HistoricalData expressions that generate their own keywords
24830        if let Some(at_before) = &e.at_before {
24831            self.write(" ");
24832            self.generate_expression(at_before)?;
24833        }
24834        if let Some(end) = &e.end {
24835            self.write(" ");
24836            self.generate_expression(end)?;
24837        }
24838        Ok(())
24839    }
24840
24841    fn generate_character_set_column_constraint(
24842        &mut self,
24843        e: &CharacterSetColumnConstraint,
24844    ) -> Result<()> {
24845        // CHARACTER SET charset_name
24846        self.write_keyword("CHARACTER SET");
24847        self.write_space();
24848        self.generate_expression(&e.this)?;
24849        Ok(())
24850    }
24851
24852    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24853        // [DEFAULT] CHARACTER SET=value
24854        if e.default.is_some() {
24855            self.write_keyword("DEFAULT");
24856            self.write_space();
24857        }
24858        self.write_keyword("CHARACTER SET");
24859        self.write("=");
24860        self.generate_expression(&e.this)?;
24861        Ok(())
24862    }
24863
24864    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24865        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24866        self.write_keyword("CHECK");
24867        self.write(" (");
24868        self.generate_expression(&e.this)?;
24869        self.write(")");
24870        if e.enforced.is_some() {
24871            self.write_space();
24872            self.write_keyword("ENFORCED");
24873        }
24874        Ok(())
24875    }
24876
24877    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24878        // CHECK_JSON(this)
24879        self.write_keyword("CHECK_JSON");
24880        self.write("(");
24881        self.generate_expression(&e.this)?;
24882        self.write(")");
24883        Ok(())
24884    }
24885
24886    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24887        // CHECK_XML(this)
24888        self.write_keyword("CHECK_XML");
24889        self.write("(");
24890        self.generate_expression(&e.this)?;
24891        self.write(")");
24892        Ok(())
24893    }
24894
24895    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24896        // CHECKSUM=[ON|OFF|DEFAULT]
24897        self.write_keyword("CHECKSUM");
24898        self.write("=");
24899        if e.on.is_some() {
24900            self.write_keyword("ON");
24901        } else if e.default.is_some() {
24902            self.write_keyword("DEFAULT");
24903        } else {
24904            self.write_keyword("OFF");
24905        }
24906        Ok(())
24907    }
24908
24909    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24910        // Python: return f"{shallow}{keyword} {this}"
24911        if e.shallow.is_some() {
24912            self.write_keyword("SHALLOW");
24913            self.write_space();
24914        }
24915        if e.copy.is_some() {
24916            self.write_keyword("COPY");
24917        } else {
24918            self.write_keyword("CLONE");
24919        }
24920        self.write_space();
24921        self.generate_expression(&e.this)?;
24922        Ok(())
24923    }
24924
24925    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24926        // CLUSTER BY (expressions)
24927        self.write_keyword("CLUSTER BY");
24928        self.write(" (");
24929        for (i, ord) in e.expressions.iter().enumerate() {
24930            if i > 0 {
24931                self.write(", ");
24932            }
24933            self.generate_ordered(ord)?;
24934        }
24935        self.write(")");
24936        Ok(())
24937    }
24938
24939    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
24940        // BigQuery table property: CLUSTER BY col1, col2
24941        self.write_keyword("CLUSTER BY");
24942        self.write_space();
24943        for (i, col) in e.columns.iter().enumerate() {
24944            if i > 0 {
24945                self.write(", ");
24946            }
24947            self.generate_identifier(col)?;
24948        }
24949        Ok(())
24950    }
24951
24952    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24953        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24954        self.write_keyword("CLUSTERED BY");
24955        self.write(" (");
24956        for (i, expr) in e.expressions.iter().enumerate() {
24957            if i > 0 {
24958                self.write(", ");
24959            }
24960            self.generate_expression(expr)?;
24961        }
24962        self.write(")");
24963        if let Some(sorted_by) = &e.sorted_by {
24964            self.write_space();
24965            self.write_keyword("SORTED BY");
24966            self.write(" (");
24967            // Unwrap Tuple to avoid double parentheses
24968            if let Expression::Tuple(t) = sorted_by.as_ref() {
24969                for (i, expr) in t.expressions.iter().enumerate() {
24970                    if i > 0 {
24971                        self.write(", ");
24972                    }
24973                    self.generate_expression(expr)?;
24974                }
24975            } else {
24976                self.generate_expression(sorted_by)?;
24977            }
24978            self.write(")");
24979        }
24980        if let Some(buckets) = &e.buckets {
24981            self.write_space();
24982            self.write_keyword("INTO");
24983            self.write_space();
24984            self.generate_expression(buckets)?;
24985            self.write_space();
24986            self.write_keyword("BUCKETS");
24987        }
24988        Ok(())
24989    }
24990
24991    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24992        // [DEFAULT] COLLATE [=] value
24993        // BigQuery uses space: DEFAULT COLLATE 'en'
24994        // Others use equals: COLLATE='en'
24995        if e.default.is_some() {
24996            self.write_keyword("DEFAULT");
24997            self.write_space();
24998        }
24999        self.write_keyword("COLLATE");
25000        // BigQuery uses space between COLLATE and value
25001        match self.config.dialect {
25002            Some(DialectType::BigQuery) => self.write_space(),
25003            _ => self.write("="),
25004        }
25005        self.generate_expression(&e.this)?;
25006        Ok(())
25007    }
25008
25009    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
25010        // ColumnConstraint is an enum
25011        match e {
25012            ColumnConstraint::NotNull => {
25013                self.write_keyword("NOT NULL");
25014            }
25015            ColumnConstraint::Null => {
25016                self.write_keyword("NULL");
25017            }
25018            ColumnConstraint::Unique => {
25019                self.write_keyword("UNIQUE");
25020            }
25021            ColumnConstraint::PrimaryKey => {
25022                self.write_keyword("PRIMARY KEY");
25023            }
25024            ColumnConstraint::Default(expr) => {
25025                self.write_keyword("DEFAULT");
25026                self.write_space();
25027                self.generate_expression(expr)?;
25028            }
25029            ColumnConstraint::Check(expr) => {
25030                self.write_keyword("CHECK");
25031                self.write(" (");
25032                self.generate_expression(expr)?;
25033                self.write(")");
25034            }
25035            ColumnConstraint::References(fk_ref) => {
25036                if fk_ref.has_foreign_key_keywords {
25037                    self.write_keyword("FOREIGN KEY");
25038                    self.write_space();
25039                }
25040                self.write_keyword("REFERENCES");
25041                self.write_space();
25042                self.generate_table(&fk_ref.table)?;
25043                if !fk_ref.columns.is_empty() {
25044                    self.write(" (");
25045                    for (i, col) in fk_ref.columns.iter().enumerate() {
25046                        if i > 0 {
25047                            self.write(", ");
25048                        }
25049                        self.generate_identifier(col)?;
25050                    }
25051                    self.write(")");
25052                }
25053            }
25054            ColumnConstraint::GeneratedAsIdentity(gen) => {
25055                self.write_keyword("GENERATED");
25056                self.write_space();
25057                if gen.always {
25058                    self.write_keyword("ALWAYS");
25059                } else {
25060                    self.write_keyword("BY DEFAULT");
25061                    if gen.on_null {
25062                        self.write_space();
25063                        self.write_keyword("ON NULL");
25064                    }
25065                }
25066                self.write_space();
25067                self.write_keyword("AS IDENTITY");
25068            }
25069            ColumnConstraint::Collate(collation) => {
25070                self.write_keyword("COLLATE");
25071                self.write_space();
25072                self.generate_identifier(collation)?;
25073            }
25074            ColumnConstraint::Comment(comment) => {
25075                self.write_keyword("COMMENT");
25076                self.write(" '");
25077                self.write(comment);
25078                self.write("'");
25079            }
25080            ColumnConstraint::ComputedColumn(cc) => {
25081                self.generate_computed_column_inline(cc)?;
25082            }
25083            ColumnConstraint::GeneratedAsRow(gar) => {
25084                self.generate_generated_as_row_inline(gar)?;
25085            }
25086            ColumnConstraint::Tags(tags) => {
25087                self.write_keyword("TAG");
25088                self.write(" (");
25089                for (i, expr) in tags.expressions.iter().enumerate() {
25090                    if i > 0 {
25091                        self.write(", ");
25092                    }
25093                    self.generate_expression(expr)?;
25094                }
25095                self.write(")");
25096            }
25097            ColumnConstraint::Path(path_expr) => {
25098                self.write_keyword("PATH");
25099                self.write_space();
25100                self.generate_expression(path_expr)?;
25101            }
25102        }
25103        Ok(())
25104    }
25105
25106    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
25107        // ColumnPosition is an enum
25108        match e {
25109            ColumnPosition::First => {
25110                self.write_keyword("FIRST");
25111            }
25112            ColumnPosition::After(ident) => {
25113                self.write_keyword("AFTER");
25114                self.write_space();
25115                self.generate_identifier(ident)?;
25116            }
25117        }
25118        Ok(())
25119    }
25120
25121    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
25122        // column(prefix)
25123        self.generate_expression(&e.this)?;
25124        self.write("(");
25125        self.generate_expression(&e.expression)?;
25126        self.write(")");
25127        Ok(())
25128    }
25129
25130    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
25131        // If unpack is true, this came from * COLUMNS(pattern)
25132        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
25133        if let Some(ref unpack) = e.unpack {
25134            if let Expression::Boolean(b) = unpack.as_ref() {
25135                if b.value {
25136                    self.write("*");
25137                }
25138            }
25139        }
25140        self.write_keyword("COLUMNS");
25141        self.write("(");
25142        self.generate_expression(&e.this)?;
25143        self.write(")");
25144        Ok(())
25145    }
25146
25147    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25148        // Combined aggregate: FUNC(args) combined
25149        self.generate_expression(&e.this)?;
25150        self.write("(");
25151        for (i, expr) in e.expressions.iter().enumerate() {
25152            if i > 0 {
25153                self.write(", ");
25154            }
25155            self.generate_expression(expr)?;
25156        }
25157        self.write(")");
25158        Ok(())
25159    }
25160
25161    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25162        // Combined parameterized aggregate: FUNC(params)(expressions)
25163        self.generate_expression(&e.this)?;
25164        self.write("(");
25165        for (i, param) in e.params.iter().enumerate() {
25166            if i > 0 {
25167                self.write(", ");
25168            }
25169            self.generate_expression(param)?;
25170        }
25171        self.write(")(");
25172        for (i, expr) in e.expressions.iter().enumerate() {
25173            if i > 0 {
25174                self.write(", ");
25175            }
25176            self.generate_expression(expr)?;
25177        }
25178        self.write(")");
25179        Ok(())
25180    }
25181
25182    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25183        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25184        self.write_keyword("COMMIT");
25185
25186        // TSQL always uses COMMIT TRANSACTION
25187        if e.this.is_none()
25188            && matches!(
25189                self.config.dialect,
25190                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25191            )
25192        {
25193            self.write_space();
25194            self.write_keyword("TRANSACTION");
25195        }
25196
25197        // Check if this has TRANSACTION keyword or transaction name
25198        if let Some(this) = &e.this {
25199            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25200            let is_transaction_marker = matches!(
25201                this.as_ref(),
25202                Expression::Identifier(id) if id.name == "TRANSACTION"
25203            );
25204
25205            self.write_space();
25206            self.write_keyword("TRANSACTION");
25207
25208            // If it's a real transaction name, output it
25209            if !is_transaction_marker {
25210                self.write_space();
25211                self.generate_expression(this)?;
25212            }
25213        }
25214
25215        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25216        if let Some(durability) = &e.durability {
25217            self.write_space();
25218            self.write_keyword("WITH");
25219            self.write(" (");
25220            self.write_keyword("DELAYED_DURABILITY");
25221            self.write(" = ");
25222            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25223                self.write_keyword("ON");
25224            } else {
25225                self.write_keyword("OFF");
25226            }
25227            self.write(")");
25228        }
25229
25230        // Output AND [NO] CHAIN
25231        if let Some(chain) = &e.chain {
25232            self.write_space();
25233            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25234                self.write_keyword("AND NO CHAIN");
25235            } else {
25236                self.write_keyword("AND CHAIN");
25237            }
25238        }
25239        Ok(())
25240    }
25241
25242    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25243        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25244        self.write("[");
25245        self.generate_expression(&e.this)?;
25246        self.write_space();
25247        self.write_keyword("FOR");
25248        self.write_space();
25249        self.generate_expression(&e.expression)?;
25250        // Handle optional position variable (for enumerate-like syntax)
25251        if let Some(pos) = &e.position {
25252            self.write(", ");
25253            self.generate_expression(pos)?;
25254        }
25255        if let Some(iterator) = &e.iterator {
25256            self.write_space();
25257            self.write_keyword("IN");
25258            self.write_space();
25259            self.generate_expression(iterator)?;
25260        }
25261        if let Some(condition) = &e.condition {
25262            self.write_space();
25263            self.write_keyword("IF");
25264            self.write_space();
25265            self.generate_expression(condition)?;
25266        }
25267        self.write("]");
25268        Ok(())
25269    }
25270
25271    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25272        // COMPRESS(this[, method])
25273        self.write_keyword("COMPRESS");
25274        self.write("(");
25275        self.generate_expression(&e.this)?;
25276        if let Some(method) = &e.method {
25277            self.write(", '");
25278            self.write(method);
25279            self.write("'");
25280        }
25281        self.write(")");
25282        Ok(())
25283    }
25284
25285    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25286        // Python: return f"COMPRESS {this}"
25287        self.write_keyword("COMPRESS");
25288        if let Some(this) = &e.this {
25289            self.write_space();
25290            self.generate_expression(this)?;
25291        }
25292        Ok(())
25293    }
25294
25295    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25296        // Python: return f"AS {this}{persisted}"
25297        self.write_keyword("AS");
25298        self.write_space();
25299        self.generate_expression(&e.this)?;
25300        if e.not_null.is_some() {
25301            self.write_space();
25302            self.write_keyword("PERSISTED NOT NULL");
25303        } else if e.persisted.is_some() {
25304            self.write_space();
25305            self.write_keyword("PERSISTED");
25306        }
25307        Ok(())
25308    }
25309
25310    /// Generate a ComputedColumn constraint inline within a column definition.
25311    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25312    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25313    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25314        let computed_expr = if matches!(
25315            self.config.dialect,
25316            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25317        ) {
25318            match &*cc.expression {
25319                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25320                {
25321                    let wrapped = Expression::Cast(Box::new(Cast {
25322                        this: y.this.clone(),
25323                        to: DataType::Date,
25324                        trailing_comments: Vec::new(),
25325                        double_colon_syntax: false,
25326                        format: None,
25327                        default: None,
25328                        inferred_type: None,
25329                    }));
25330                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25331                }
25332                Expression::Function(f)
25333                    if f.name.eq_ignore_ascii_case("YEAR")
25334                        && f.args.len() == 1
25335                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25336                {
25337                    let wrapped = Expression::Cast(Box::new(Cast {
25338                        this: f.args[0].clone(),
25339                        to: DataType::Date,
25340                        trailing_comments: Vec::new(),
25341                        double_colon_syntax: false,
25342                        format: None,
25343                        default: None,
25344                        inferred_type: None,
25345                    }));
25346                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25347                }
25348                _ => *cc.expression.clone(),
25349            }
25350        } else {
25351            *cc.expression.clone()
25352        };
25353
25354        match cc.persistence_kind.as_deref() {
25355            Some("STORED") | Some("VIRTUAL") => {
25356                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25357                self.write_keyword("GENERATED ALWAYS AS");
25358                self.write(" (");
25359                self.generate_expression(&computed_expr)?;
25360                self.write(")");
25361                self.write_space();
25362                if cc.persisted {
25363                    self.write_keyword("STORED");
25364                } else {
25365                    self.write_keyword("VIRTUAL");
25366                }
25367            }
25368            Some("PERSISTED") => {
25369                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25370                self.write_keyword("AS");
25371                self.write(" (");
25372                self.generate_expression(&computed_expr)?;
25373                self.write(")");
25374                self.write_space();
25375                self.write_keyword("PERSISTED");
25376                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25377                if let Some(ref dt) = cc.data_type {
25378                    self.write_space();
25379                    self.generate_data_type(dt)?;
25380                }
25381                if cc.not_null {
25382                    self.write_space();
25383                    self.write_keyword("NOT NULL");
25384                }
25385            }
25386            _ => {
25387                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25388                // TSQL computed column without PERSISTED: AS (expr)
25389                if matches!(
25390                    self.config.dialect,
25391                    Some(DialectType::Spark)
25392                        | Some(DialectType::Databricks)
25393                        | Some(DialectType::Hive)
25394                ) {
25395                    self.write_keyword("GENERATED ALWAYS AS");
25396                    self.write(" (");
25397                    self.generate_expression(&computed_expr)?;
25398                    self.write(")");
25399                } else if matches!(
25400                    self.config.dialect,
25401                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25402                ) {
25403                    self.write_keyword("AS");
25404                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25405                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25406                    if omit_parens {
25407                        self.write_space();
25408                        self.generate_expression(&computed_expr)?;
25409                    } else {
25410                        self.write(" (");
25411                        self.generate_expression(&computed_expr)?;
25412                        self.write(")");
25413                    }
25414                } else {
25415                    self.write_keyword("AS");
25416                    self.write(" (");
25417                    self.generate_expression(&computed_expr)?;
25418                    self.write(")");
25419                }
25420            }
25421        }
25422        Ok(())
25423    }
25424
25425    /// Generate a GeneratedAsRow constraint inline within a column definition.
25426    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25427    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25428        self.write_keyword("GENERATED ALWAYS AS ROW ");
25429        if gar.start {
25430            self.write_keyword("START");
25431        } else {
25432            self.write_keyword("END");
25433        }
25434        if gar.hidden {
25435            self.write_space();
25436            self.write_keyword("HIDDEN");
25437        }
25438        Ok(())
25439    }
25440
25441    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25442    fn generate_system_versioning_content(
25443        &mut self,
25444        e: &WithSystemVersioningProperty,
25445    ) -> Result<()> {
25446        let mut parts = Vec::new();
25447
25448        if let Some(this) = &e.this {
25449            let mut s = String::from("HISTORY_TABLE=");
25450            let mut gen = Generator::new();
25451            gen.config = self.config.clone();
25452            gen.generate_expression(this)?;
25453            s.push_str(&gen.output);
25454            parts.push(s);
25455        }
25456
25457        if let Some(data_consistency) = &e.data_consistency {
25458            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25459            let mut gen = Generator::new();
25460            gen.config = self.config.clone();
25461            gen.generate_expression(data_consistency)?;
25462            s.push_str(&gen.output);
25463            parts.push(s);
25464        }
25465
25466        if let Some(retention_period) = &e.retention_period {
25467            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25468            let mut gen = Generator::new();
25469            gen.config = self.config.clone();
25470            gen.generate_expression(retention_period)?;
25471            s.push_str(&gen.output);
25472            parts.push(s);
25473        }
25474
25475        self.write_keyword("SYSTEM_VERSIONING");
25476        self.write("=");
25477
25478        if !parts.is_empty() {
25479            self.write_keyword("ON");
25480            self.write("(");
25481            self.write(&parts.join(", "));
25482            self.write(")");
25483        } else if e.on.is_some() {
25484            self.write_keyword("ON");
25485        } else {
25486            self.write_keyword("OFF");
25487        }
25488
25489        Ok(())
25490    }
25491
25492    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25493        // Conditional INSERT for multi-table inserts
25494        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25495        if e.else_.is_some() {
25496            self.write_keyword("ELSE");
25497            self.write_space();
25498        } else if let Some(expression) = &e.expression {
25499            self.write_keyword("WHEN");
25500            self.write_space();
25501            self.generate_expression(expression)?;
25502            self.write_space();
25503            self.write_keyword("THEN");
25504            self.write_space();
25505        }
25506
25507        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25508        // without the "INSERT " prefix
25509        if let Expression::Insert(insert) = e.this.as_ref() {
25510            self.write_keyword("INTO");
25511            self.write_space();
25512            self.generate_table(&insert.table)?;
25513
25514            // Optional column list
25515            if !insert.columns.is_empty() {
25516                self.write(" (");
25517                for (i, col) in insert.columns.iter().enumerate() {
25518                    if i > 0 {
25519                        self.write(", ");
25520                    }
25521                    self.generate_identifier(col)?;
25522                }
25523                self.write(")");
25524            }
25525
25526            // Optional VALUES clause
25527            if !insert.values.is_empty() {
25528                self.write_space();
25529                self.write_keyword("VALUES");
25530                for (row_idx, row) in insert.values.iter().enumerate() {
25531                    if row_idx > 0 {
25532                        self.write(", ");
25533                    }
25534                    self.write(" (");
25535                    for (i, val) in row.iter().enumerate() {
25536                        if i > 0 {
25537                            self.write(", ");
25538                        }
25539                        self.generate_expression(val)?;
25540                    }
25541                    self.write(")");
25542                }
25543            }
25544        } else {
25545            // Fallback for non-Insert expressions
25546            self.generate_expression(&e.this)?;
25547        }
25548        Ok(())
25549    }
25550
25551    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25552        // Python: return f"CONSTRAINT {this} {expressions}"
25553        self.write_keyword("CONSTRAINT");
25554        self.write_space();
25555        self.generate_expression(&e.this)?;
25556        if !e.expressions.is_empty() {
25557            self.write_space();
25558            for (i, expr) in e.expressions.iter().enumerate() {
25559                if i > 0 {
25560                    self.write_space();
25561                }
25562                self.generate_expression(expr)?;
25563            }
25564        }
25565        Ok(())
25566    }
25567
25568    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25569        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25570        self.write_keyword("CONVERT_TIMEZONE");
25571        self.write("(");
25572        let mut first = true;
25573        if let Some(source_tz) = &e.source_tz {
25574            self.generate_expression(source_tz)?;
25575            first = false;
25576        }
25577        if let Some(target_tz) = &e.target_tz {
25578            if !first {
25579                self.write(", ");
25580            }
25581            self.generate_expression(target_tz)?;
25582            first = false;
25583        }
25584        if let Some(timestamp) = &e.timestamp {
25585            if !first {
25586                self.write(", ");
25587            }
25588            self.generate_expression(timestamp)?;
25589        }
25590        self.write(")");
25591        Ok(())
25592    }
25593
25594    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25595        // CONVERT(this USING dest)
25596        self.write_keyword("CONVERT");
25597        self.write("(");
25598        self.generate_expression(&e.this)?;
25599        if let Some(dest) = &e.dest {
25600            self.write_space();
25601            self.write_keyword("USING");
25602            self.write_space();
25603            self.generate_expression(dest)?;
25604        }
25605        self.write(")");
25606        Ok(())
25607    }
25608
25609    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25610        self.write_keyword("COPY");
25611        if e.is_into {
25612            self.write_space();
25613            self.write_keyword("INTO");
25614        }
25615        self.write_space();
25616
25617        // Generate target table or query (or stage for COPY INTO @stage)
25618        if let Expression::Literal(Literal::String(s)) = &e.this {
25619            if s.starts_with('@') {
25620                self.write(s);
25621            } else {
25622                self.generate_expression(&e.this)?;
25623            }
25624        } else {
25625            self.generate_expression(&e.this)?;
25626        }
25627
25628        // FROM or TO based on kind
25629        if e.kind {
25630            // kind=true means FROM (loading into table)
25631            if self.config.pretty {
25632                self.write_newline();
25633            } else {
25634                self.write_space();
25635            }
25636            self.write_keyword("FROM");
25637            self.write_space();
25638        } else if !e.files.is_empty() {
25639            // kind=false means TO (exporting)
25640            if self.config.pretty {
25641                self.write_newline();
25642            } else {
25643                self.write_space();
25644            }
25645            self.write_keyword("TO");
25646            self.write_space();
25647        }
25648
25649        // Generate source/destination files
25650        for (i, file) in e.files.iter().enumerate() {
25651            if i > 0 {
25652                self.write_space();
25653            }
25654            // For stage references (strings starting with @), output without quotes
25655            if let Expression::Literal(Literal::String(s)) = file {
25656                if s.starts_with('@') {
25657                    self.write(s);
25658                } else {
25659                    self.generate_expression(file)?;
25660                }
25661            } else if let Expression::Identifier(id) = file {
25662                // Backtick-quoted file path (Databricks style: `s3://link`)
25663                if id.quoted {
25664                    self.write("`");
25665                    self.write(&id.name);
25666                    self.write("`");
25667                } else {
25668                    self.generate_expression(file)?;
25669                }
25670            } else {
25671                self.generate_expression(file)?;
25672            }
25673        }
25674
25675        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25676        if !e.with_wrapped {
25677            if let Some(ref creds) = e.credentials {
25678                if let Some(ref storage) = creds.storage {
25679                    if self.config.pretty {
25680                        self.write_newline();
25681                    } else {
25682                        self.write_space();
25683                    }
25684                    self.write_keyword("STORAGE_INTEGRATION");
25685                    self.write(" = ");
25686                    self.write(storage);
25687                }
25688                if creds.credentials.is_empty() {
25689                    // Empty credentials: CREDENTIALS = ()
25690                    if self.config.pretty {
25691                        self.write_newline();
25692                    } else {
25693                        self.write_space();
25694                    }
25695                    self.write_keyword("CREDENTIALS");
25696                    self.write(" = ()");
25697                } else {
25698                    if self.config.pretty {
25699                        self.write_newline();
25700                    } else {
25701                        self.write_space();
25702                    }
25703                    self.write_keyword("CREDENTIALS");
25704                    // Check if this is Redshift-style (single value with empty key)
25705                    // vs Snowflake-style (multiple key=value pairs)
25706                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25707                        // Redshift style: CREDENTIALS 'value'
25708                        self.write(" '");
25709                        self.write(&creds.credentials[0].1);
25710                        self.write("'");
25711                    } else {
25712                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25713                        self.write(" = (");
25714                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25715                            if i > 0 {
25716                                self.write_space();
25717                            }
25718                            self.write(k);
25719                            self.write("='");
25720                            self.write(v);
25721                            self.write("'");
25722                        }
25723                        self.write(")");
25724                    }
25725                }
25726                if let Some(ref encryption) = creds.encryption {
25727                    self.write_space();
25728                    self.write_keyword("ENCRYPTION");
25729                    self.write(" = ");
25730                    self.write(encryption);
25731                }
25732            }
25733        }
25734
25735        // Generate parameters
25736        if !e.params.is_empty() {
25737            if e.with_wrapped {
25738                // DuckDB/PostgreSQL/TSQL WITH (...) format
25739                self.write_space();
25740                self.write_keyword("WITH");
25741                self.write(" (");
25742                for (i, param) in e.params.iter().enumerate() {
25743                    if i > 0 {
25744                        self.write(", ");
25745                    }
25746                    self.generate_copy_param_with_format(param)?;
25747                }
25748                self.write(")");
25749            } else {
25750                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25751                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25752                // For Snowflake: KEY = VALUE
25753                for param in &e.params {
25754                    if self.config.pretty {
25755                        self.write_newline();
25756                    } else {
25757                        self.write_space();
25758                    }
25759                    // Preserve original case of parameter name (important for Redshift COPY options)
25760                    self.write(&param.name);
25761                    if let Some(ref value) = param.value {
25762                        // Use = only if it was present in the original (param.eq)
25763                        if param.eq {
25764                            self.write(" = ");
25765                        } else {
25766                            self.write(" ");
25767                        }
25768                        if !param.values.is_empty() {
25769                            self.write("(");
25770                            for (i, v) in param.values.iter().enumerate() {
25771                                if i > 0 {
25772                                    self.write_space();
25773                                }
25774                                self.generate_copy_nested_param(v)?;
25775                            }
25776                            self.write(")");
25777                        } else {
25778                            // For COPY parameter values, output identifiers without quoting
25779                            self.generate_copy_param_value(value)?;
25780                        }
25781                    } else if !param.values.is_empty() {
25782                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25783                        if param.eq {
25784                            self.write(" = (");
25785                        } else {
25786                            self.write(" (");
25787                        }
25788                        // Determine separator for values inside parentheses:
25789                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25790                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25791                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25792                        let is_key_value_pairs = param
25793                            .values
25794                            .first()
25795                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25796                        let sep = if is_key_value_pairs && param.eq {
25797                            " "
25798                        } else {
25799                            ", "
25800                        };
25801                        for (i, v) in param.values.iter().enumerate() {
25802                            if i > 0 {
25803                                self.write(sep);
25804                            }
25805                            self.generate_copy_nested_param(v)?;
25806                        }
25807                        self.write(")");
25808                    }
25809                }
25810            }
25811        }
25812
25813        Ok(())
25814    }
25815
25816    /// Generate a COPY parameter in WITH (...) format
25817    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25818    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25819        self.write_keyword(&param.name);
25820        if !param.values.is_empty() {
25821            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25822            self.write(" = (");
25823            for (i, v) in param.values.iter().enumerate() {
25824                if i > 0 {
25825                    self.write(", ");
25826                }
25827                self.generate_copy_nested_param(v)?;
25828            }
25829            self.write(")");
25830        } else if let Some(ref value) = param.value {
25831            if param.eq {
25832                self.write(" = ");
25833            } else {
25834                self.write(" ");
25835            }
25836            self.generate_expression(value)?;
25837        }
25838        Ok(())
25839    }
25840
25841    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25842    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25843        match expr {
25844            Expression::Eq(eq) => {
25845                // Generate key
25846                match &eq.left {
25847                    Expression::Column(c) => self.write(&c.name.name),
25848                    _ => self.generate_expression(&eq.left)?,
25849                }
25850                self.write("=");
25851                // Generate value
25852                match &eq.right {
25853                    Expression::Literal(Literal::String(s)) => {
25854                        self.write("'");
25855                        self.write(s);
25856                        self.write("'");
25857                    }
25858                    Expression::Tuple(t) => {
25859                        // For lists like NULL_IF=('', 'str1')
25860                        self.write("(");
25861                        if self.config.pretty {
25862                            self.write_newline();
25863                            self.indent_level += 1;
25864                            for (i, item) in t.expressions.iter().enumerate() {
25865                                if i > 0 {
25866                                    self.write(", ");
25867                                }
25868                                self.write_indent();
25869                                self.generate_expression(item)?;
25870                            }
25871                            self.write_newline();
25872                            self.indent_level -= 1;
25873                        } else {
25874                            for (i, item) in t.expressions.iter().enumerate() {
25875                                if i > 0 {
25876                                    self.write(", ");
25877                                }
25878                                self.generate_expression(item)?;
25879                            }
25880                        }
25881                        self.write(")");
25882                    }
25883                    _ => self.generate_expression(&eq.right)?,
25884                }
25885                Ok(())
25886            }
25887            Expression::Column(c) => {
25888                // Standalone keyword like COMPRESSION
25889                self.write(&c.name.name);
25890                Ok(())
25891            }
25892            _ => self.generate_expression(expr),
25893        }
25894    }
25895
25896    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25897    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25898    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25899        match expr {
25900            Expression::Column(c) => {
25901                // Output identifier, preserving quotes if originally quoted
25902                if c.name.quoted {
25903                    self.write("\"");
25904                    self.write(&c.name.name);
25905                    self.write("\"");
25906                } else {
25907                    self.write(&c.name.name);
25908                }
25909                Ok(())
25910            }
25911            Expression::Identifier(id) => {
25912                // Output identifier, preserving quotes if originally quoted
25913                if id.quoted {
25914                    self.write("\"");
25915                    self.write(&id.name);
25916                    self.write("\"");
25917                } else {
25918                    self.write(&id.name);
25919                }
25920                Ok(())
25921            }
25922            Expression::Literal(Literal::String(s)) => {
25923                // Output string with quotes
25924                self.write("'");
25925                self.write(s);
25926                self.write("'");
25927                Ok(())
25928            }
25929            _ => self.generate_expression(expr),
25930        }
25931    }
25932
25933    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25934        self.write_keyword(&e.name);
25935        if let Some(ref value) = e.value {
25936            if e.eq {
25937                self.write(" = ");
25938            } else {
25939                self.write(" ");
25940            }
25941            self.generate_expression(value)?;
25942        }
25943        if !e.values.is_empty() {
25944            if e.eq {
25945                self.write(" = ");
25946            } else {
25947                self.write(" ");
25948            }
25949            self.write("(");
25950            for (i, v) in e.values.iter().enumerate() {
25951                if i > 0 {
25952                    self.write(", ");
25953                }
25954                self.generate_expression(v)?;
25955            }
25956            self.write(")");
25957        }
25958        Ok(())
25959    }
25960
25961    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25962        // CORR(this, expression)
25963        self.write_keyword("CORR");
25964        self.write("(");
25965        self.generate_expression(&e.this)?;
25966        self.write(", ");
25967        self.generate_expression(&e.expression)?;
25968        self.write(")");
25969        Ok(())
25970    }
25971
25972    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25973        // COSINE_DISTANCE(this, expression)
25974        self.write_keyword("COSINE_DISTANCE");
25975        self.write("(");
25976        self.generate_expression(&e.this)?;
25977        self.write(", ");
25978        self.generate_expression(&e.expression)?;
25979        self.write(")");
25980        Ok(())
25981    }
25982
25983    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25984        // COVAR_POP(this, expression)
25985        self.write_keyword("COVAR_POP");
25986        self.write("(");
25987        self.generate_expression(&e.this)?;
25988        self.write(", ");
25989        self.generate_expression(&e.expression)?;
25990        self.write(")");
25991        Ok(())
25992    }
25993
25994    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25995        // COVAR_SAMP(this, expression)
25996        self.write_keyword("COVAR_SAMP");
25997        self.write("(");
25998        self.generate_expression(&e.this)?;
25999        self.write(", ");
26000        self.generate_expression(&e.expression)?;
26001        self.write(")");
26002        Ok(())
26003    }
26004
26005    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
26006        // CREDENTIALS (key1='value1', key2='value2')
26007        self.write_keyword("CREDENTIALS");
26008        self.write(" (");
26009        for (i, (key, value)) in e.credentials.iter().enumerate() {
26010            if i > 0 {
26011                self.write(", ");
26012            }
26013            self.write(key);
26014            self.write("='");
26015            self.write(value);
26016            self.write("'");
26017        }
26018        self.write(")");
26019        Ok(())
26020    }
26021
26022    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
26023        // CREDENTIALS=(expressions)
26024        self.write_keyword("CREDENTIALS");
26025        self.write("=(");
26026        for (i, expr) in e.expressions.iter().enumerate() {
26027            if i > 0 {
26028                self.write(", ");
26029            }
26030            self.generate_expression(expr)?;
26031        }
26032        self.write(")");
26033        Ok(())
26034    }
26035
26036    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
26037        use crate::dialects::DialectType;
26038
26039        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
26040        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
26041        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
26042            self.generate_expression(&e.this)?;
26043            self.write_space();
26044            self.write_keyword("AS");
26045            self.write_space();
26046            self.generate_identifier(&e.alias)?;
26047            return Ok(());
26048        }
26049        self.write(&e.alias.name);
26050
26051        // BigQuery doesn't support column aliases in CTE definitions
26052        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
26053
26054        if !e.columns.is_empty() && !skip_cte_columns {
26055            self.write("(");
26056            for (i, col) in e.columns.iter().enumerate() {
26057                if i > 0 {
26058                    self.write(", ");
26059                }
26060                self.write(&col.name);
26061            }
26062            self.write(")");
26063        }
26064        // USING KEY (columns) for DuckDB recursive CTEs
26065        if !e.key_expressions.is_empty() {
26066            self.write_space();
26067            self.write_keyword("USING KEY");
26068            self.write(" (");
26069            for (i, key) in e.key_expressions.iter().enumerate() {
26070                if i > 0 {
26071                    self.write(", ");
26072                }
26073                self.write(&key.name);
26074            }
26075            self.write(")");
26076        }
26077        self.write_space();
26078        self.write_keyword("AS");
26079        self.write_space();
26080        if let Some(materialized) = e.materialized {
26081            if materialized {
26082                self.write_keyword("MATERIALIZED");
26083            } else {
26084                self.write_keyword("NOT MATERIALIZED");
26085            }
26086            self.write_space();
26087        }
26088        self.write("(");
26089        self.generate_expression(&e.this)?;
26090        self.write(")");
26091        Ok(())
26092    }
26093
26094    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
26095        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
26096        if e.expressions.is_empty() {
26097            self.write_keyword("WITH CUBE");
26098        } else {
26099            self.write_keyword("CUBE");
26100            self.write("(");
26101            for (i, expr) in e.expressions.iter().enumerate() {
26102                if i > 0 {
26103                    self.write(", ");
26104                }
26105                self.generate_expression(expr)?;
26106            }
26107            self.write(")");
26108        }
26109        Ok(())
26110    }
26111
26112    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
26113        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
26114        self.write_keyword("CURRENT_DATETIME");
26115        if let Some(this) = &e.this {
26116            self.write("(");
26117            self.generate_expression(this)?;
26118            self.write(")");
26119        }
26120        Ok(())
26121    }
26122
26123    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
26124        // CURRENT_SCHEMA - no arguments
26125        self.write_keyword("CURRENT_SCHEMA");
26126        Ok(())
26127    }
26128
26129    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
26130        // CURRENT_SCHEMAS(include_implicit)
26131        self.write_keyword("CURRENT_SCHEMAS");
26132        self.write("(");
26133        if let Some(this) = &e.this {
26134            self.generate_expression(this)?;
26135        }
26136        self.write(")");
26137        Ok(())
26138    }
26139
26140    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
26141        // CURRENT_USER or CURRENT_USER()
26142        self.write_keyword("CURRENT_USER");
26143        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
26144        let needs_parens = e.this.is_some()
26145            || matches!(
26146                self.config.dialect,
26147                Some(DialectType::Snowflake)
26148                    | Some(DialectType::Spark)
26149                    | Some(DialectType::Hive)
26150                    | Some(DialectType::DuckDB)
26151                    | Some(DialectType::BigQuery)
26152                    | Some(DialectType::MySQL)
26153                    | Some(DialectType::Databricks)
26154            );
26155        if needs_parens {
26156            self.write("()");
26157        }
26158        Ok(())
26159    }
26160
26161    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26162        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26163        if self.config.dialect == Some(DialectType::Solr) {
26164            self.generate_expression(&e.this)?;
26165            self.write(" ");
26166            self.write_keyword("OR");
26167            self.write(" ");
26168            self.generate_expression(&e.expression)?;
26169        } else if self.config.dialect == Some(DialectType::MySQL) {
26170            self.generate_mysql_concat_from_dpipe(e)?;
26171        } else {
26172            // String concatenation: this || expression
26173            self.generate_expression(&e.this)?;
26174            self.write(" || ");
26175            self.generate_expression(&e.expression)?;
26176        }
26177        Ok(())
26178    }
26179
26180    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26181        // DATABLOCKSIZE=... (Teradata)
26182        self.write_keyword("DATABLOCKSIZE");
26183        self.write("=");
26184        if let Some(size) = e.size {
26185            self.write(&size.to_string());
26186            if let Some(units) = &e.units {
26187                self.write_space();
26188                self.generate_expression(units)?;
26189            }
26190        } else if e.minimum.is_some() {
26191            self.write_keyword("MINIMUM");
26192        } else if e.maximum.is_some() {
26193            self.write_keyword("MAXIMUM");
26194        } else if e.default.is_some() {
26195            self.write_keyword("DEFAULT");
26196        }
26197        Ok(())
26198    }
26199
26200    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26201        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26202        self.write_keyword("DATA_DELETION");
26203        self.write("=");
26204
26205        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26206        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26207
26208        if is_on {
26209            self.write_keyword("ON");
26210            if has_options {
26211                self.write("(");
26212                let mut first = true;
26213                if let Some(filter_column) = &e.filter_column {
26214                    self.write_keyword("FILTER_COLUMN");
26215                    self.write("=");
26216                    self.generate_expression(filter_column)?;
26217                    first = false;
26218                }
26219                if let Some(retention_period) = &e.retention_period {
26220                    if !first {
26221                        self.write(", ");
26222                    }
26223                    self.write_keyword("RETENTION_PERIOD");
26224                    self.write("=");
26225                    self.generate_expression(retention_period)?;
26226                }
26227                self.write(")");
26228            }
26229        } else {
26230            self.write_keyword("OFF");
26231        }
26232        Ok(())
26233    }
26234
26235    /// Generate a Date function expression
26236    /// For Exasol: {d'value'} -> TO_DATE('value')
26237    /// For other dialects: DATE('value')
26238    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26239        use crate::dialects::DialectType;
26240        use crate::expressions::Literal;
26241
26242        match self.config.dialect {
26243            // Exasol uses TO_DATE for Date expressions
26244            Some(DialectType::Exasol) => {
26245                self.write_keyword("TO_DATE");
26246                self.write("(");
26247                // Extract the string value from the expression if it's a string literal
26248                match &e.this {
26249                    Expression::Literal(Literal::String(s)) => {
26250                        self.write("'");
26251                        self.write(s);
26252                        self.write("'");
26253                    }
26254                    _ => {
26255                        self.generate_expression(&e.this)?;
26256                    }
26257                }
26258                self.write(")");
26259            }
26260            // Standard: DATE(value)
26261            _ => {
26262                self.write_keyword("DATE");
26263                self.write("(");
26264                self.generate_expression(&e.this)?;
26265                self.write(")");
26266            }
26267        }
26268        Ok(())
26269    }
26270
26271    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26272        // DATE_BIN(interval, timestamp[, origin])
26273        self.write_keyword("DATE_BIN");
26274        self.write("(");
26275        self.generate_expression(&e.this)?;
26276        self.write(", ");
26277        self.generate_expression(&e.expression)?;
26278        if let Some(origin) = &e.origin {
26279            self.write(", ");
26280            self.generate_expression(origin)?;
26281        }
26282        self.write(")");
26283        Ok(())
26284    }
26285
26286    fn generate_date_format_column_constraint(
26287        &mut self,
26288        e: &DateFormatColumnConstraint,
26289    ) -> Result<()> {
26290        // FORMAT 'format_string' (Teradata)
26291        self.write_keyword("FORMAT");
26292        self.write_space();
26293        self.generate_expression(&e.this)?;
26294        Ok(())
26295    }
26296
26297    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26298        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26299        self.write_keyword("DATE_FROM_PARTS");
26300        self.write("(");
26301        let mut first = true;
26302        if let Some(year) = &e.year {
26303            self.generate_expression(year)?;
26304            first = false;
26305        }
26306        if let Some(month) = &e.month {
26307            if !first {
26308                self.write(", ");
26309            }
26310            self.generate_expression(month)?;
26311            first = false;
26312        }
26313        if let Some(day) = &e.day {
26314            if !first {
26315                self.write(", ");
26316            }
26317            self.generate_expression(day)?;
26318        }
26319        self.write(")");
26320        Ok(())
26321    }
26322
26323    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26324        // DATETIME(this) or DATETIME(this, expression)
26325        self.write_keyword("DATETIME");
26326        self.write("(");
26327        self.generate_expression(&e.this)?;
26328        if let Some(expr) = &e.expression {
26329            self.write(", ");
26330            self.generate_expression(expr)?;
26331        }
26332        self.write(")");
26333        Ok(())
26334    }
26335
26336    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26337        // DATETIME_ADD(this, expression, unit)
26338        self.write_keyword("DATETIME_ADD");
26339        self.write("(");
26340        self.generate_expression(&e.this)?;
26341        self.write(", ");
26342        self.generate_expression(&e.expression)?;
26343        if let Some(unit) = &e.unit {
26344            self.write(", ");
26345            self.write_keyword(unit);
26346        }
26347        self.write(")");
26348        Ok(())
26349    }
26350
26351    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26352        // DATETIME_DIFF(this, expression, unit)
26353        self.write_keyword("DATETIME_DIFF");
26354        self.write("(");
26355        self.generate_expression(&e.this)?;
26356        self.write(", ");
26357        self.generate_expression(&e.expression)?;
26358        if let Some(unit) = &e.unit {
26359            self.write(", ");
26360            self.write_keyword(unit);
26361        }
26362        self.write(")");
26363        Ok(())
26364    }
26365
26366    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26367        // DATETIME_SUB(this, expression, unit)
26368        self.write_keyword("DATETIME_SUB");
26369        self.write("(");
26370        self.generate_expression(&e.this)?;
26371        self.write(", ");
26372        self.generate_expression(&e.expression)?;
26373        if let Some(unit) = &e.unit {
26374            self.write(", ");
26375            self.write_keyword(unit);
26376        }
26377        self.write(")");
26378        Ok(())
26379    }
26380
26381    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26382        // DATETIME_TRUNC(this, unit, zone)
26383        self.write_keyword("DATETIME_TRUNC");
26384        self.write("(");
26385        self.generate_expression(&e.this)?;
26386        self.write(", ");
26387        self.write_keyword(&e.unit);
26388        if let Some(zone) = &e.zone {
26389            self.write(", ");
26390            self.generate_expression(zone)?;
26391        }
26392        self.write(")");
26393        Ok(())
26394    }
26395
26396    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26397        // DAYNAME(this)
26398        self.write_keyword("DAYNAME");
26399        self.write("(");
26400        self.generate_expression(&e.this)?;
26401        self.write(")");
26402        Ok(())
26403    }
26404
26405    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26406        // DECLARE var1 AS type1, var2 AS type2, ...
26407        self.write_keyword("DECLARE");
26408        self.write_space();
26409        for (i, expr) in e.expressions.iter().enumerate() {
26410            if i > 0 {
26411                self.write(", ");
26412            }
26413            self.generate_expression(expr)?;
26414        }
26415        Ok(())
26416    }
26417
26418    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26419        use crate::dialects::DialectType;
26420
26421        // variable TYPE [DEFAULT default]
26422        self.generate_expression(&e.this)?;
26423        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26424        for name in &e.additional_names {
26425            self.write(", ");
26426            self.generate_expression(name)?;
26427        }
26428        if let Some(kind) = &e.kind {
26429            self.write_space();
26430            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26431            // TSQL: Always includes AS (normalization)
26432            // Others: Include AS if present in original
26433            match self.config.dialect {
26434                Some(DialectType::BigQuery) => {
26435                    self.write(kind);
26436                }
26437                Some(DialectType::TSQL) => {
26438                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26439                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26440                    // NONCLUSTERED, or INDEX constraints
26441                    let is_complex_table = kind.starts_with("TABLE")
26442                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26443
26444                    if is_complex_table {
26445                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26446                        self.write(kind);
26447                    } else {
26448                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26449                        if !kind.starts_with("CURSOR") {
26450                            self.write_keyword("AS");
26451                            self.write_space();
26452                        }
26453                        // Normalize INT to INTEGER for TSQL DECLARE statements
26454                        if kind == "INT" {
26455                            self.write("INTEGER");
26456                        } else if kind.starts_with("TABLE") {
26457                            // Normalize INT to INTEGER inside TABLE column definitions
26458                            let normalized = kind
26459                                .replace(" INT ", " INTEGER ")
26460                                .replace(" INT,", " INTEGER,")
26461                                .replace(" INT)", " INTEGER)")
26462                                .replace("(INT ", "(INTEGER ");
26463                            self.write(&normalized);
26464                        } else {
26465                            self.write(kind);
26466                        }
26467                    }
26468                }
26469                _ => {
26470                    if e.has_as {
26471                        self.write_keyword("AS");
26472                        self.write_space();
26473                    }
26474                    self.write(kind);
26475                }
26476            }
26477        }
26478        if let Some(default) = &e.default {
26479            // BigQuery uses DEFAULT, others use =
26480            match self.config.dialect {
26481                Some(DialectType::BigQuery) => {
26482                    self.write_space();
26483                    self.write_keyword("DEFAULT");
26484                    self.write_space();
26485                }
26486                _ => {
26487                    self.write(" = ");
26488                }
26489            }
26490            self.generate_expression(default)?;
26491        }
26492        Ok(())
26493    }
26494
26495    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26496        // DECODE(expr, search1, result1, search2, result2, ..., default)
26497        self.write_keyword("DECODE");
26498        self.write("(");
26499        for (i, expr) in e.expressions.iter().enumerate() {
26500            if i > 0 {
26501                self.write(", ");
26502            }
26503            self.generate_expression(expr)?;
26504        }
26505        self.write(")");
26506        Ok(())
26507    }
26508
26509    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26510        // DECOMPRESS(expr, 'method')
26511        self.write_keyword("DECOMPRESS");
26512        self.write("(");
26513        self.generate_expression(&e.this)?;
26514        self.write(", '");
26515        self.write(&e.method);
26516        self.write("')");
26517        Ok(())
26518    }
26519
26520    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26521        // DECOMPRESS(expr, 'method')
26522        self.write_keyword("DECOMPRESS");
26523        self.write("(");
26524        self.generate_expression(&e.this)?;
26525        self.write(", '");
26526        self.write(&e.method);
26527        self.write("')");
26528        Ok(())
26529    }
26530
26531    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26532        // DECRYPT(value, passphrase [, aad [, algorithm]])
26533        self.write_keyword("DECRYPT");
26534        self.write("(");
26535        self.generate_expression(&e.this)?;
26536        if let Some(passphrase) = &e.passphrase {
26537            self.write(", ");
26538            self.generate_expression(passphrase)?;
26539        }
26540        if let Some(aad) = &e.aad {
26541            self.write(", ");
26542            self.generate_expression(aad)?;
26543        }
26544        if let Some(method) = &e.encryption_method {
26545            self.write(", ");
26546            self.generate_expression(method)?;
26547        }
26548        self.write(")");
26549        Ok(())
26550    }
26551
26552    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26553        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26554        self.write_keyword("DECRYPT_RAW");
26555        self.write("(");
26556        self.generate_expression(&e.this)?;
26557        if let Some(key) = &e.key {
26558            self.write(", ");
26559            self.generate_expression(key)?;
26560        }
26561        if let Some(iv) = &e.iv {
26562            self.write(", ");
26563            self.generate_expression(iv)?;
26564        }
26565        if let Some(aad) = &e.aad {
26566            self.write(", ");
26567            self.generate_expression(aad)?;
26568        }
26569        if let Some(method) = &e.encryption_method {
26570            self.write(", ");
26571            self.generate_expression(method)?;
26572        }
26573        self.write(")");
26574        Ok(())
26575    }
26576
26577    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26578        // DEFINER = user
26579        self.write_keyword("DEFINER");
26580        self.write(" = ");
26581        self.generate_expression(&e.this)?;
26582        Ok(())
26583    }
26584
26585    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26586        // Python: DETACH[DATABASE IF EXISTS] this
26587        self.write_keyword("DETACH");
26588        if e.exists {
26589            self.write_keyword(" DATABASE IF EXISTS");
26590        }
26591        self.write_space();
26592        self.generate_expression(&e.this)?;
26593        Ok(())
26594    }
26595
26596    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26597        let property_name = match e.this.as_ref() {
26598            Expression::Identifier(id) => id.name.as_str(),
26599            Expression::Var(v) => v.this.as_str(),
26600            _ => "DICTIONARY",
26601        };
26602        self.write_keyword(property_name);
26603        self.write("(");
26604        self.write(&e.kind);
26605        if let Some(settings) = &e.settings {
26606            self.write("(");
26607            if let Expression::Tuple(t) = settings.as_ref() {
26608                if self.config.pretty && !t.expressions.is_empty() {
26609                    self.write_newline();
26610                    self.indent_level += 1;
26611                    for (i, pair) in t.expressions.iter().enumerate() {
26612                        if i > 0 {
26613                            self.write(",");
26614                            self.write_newline();
26615                        }
26616                        self.write_indent();
26617                        if let Expression::Tuple(pair_tuple) = pair {
26618                            if let Some(k) = pair_tuple.expressions.first() {
26619                                self.generate_expression(k)?;
26620                            }
26621                            if let Some(v) = pair_tuple.expressions.get(1) {
26622                                self.write(" ");
26623                                self.generate_expression(v)?;
26624                            }
26625                        } else {
26626                            self.generate_expression(pair)?;
26627                        }
26628                    }
26629                    self.indent_level -= 1;
26630                    self.write_newline();
26631                    self.write_indent();
26632                } else {
26633                    for (i, pair) in t.expressions.iter().enumerate() {
26634                        if i > 0 {
26635                            self.write(", ");
26636                        }
26637                        if let Expression::Tuple(pair_tuple) = pair {
26638                            if let Some(k) = pair_tuple.expressions.first() {
26639                                self.generate_expression(k)?;
26640                            }
26641                            if let Some(v) = pair_tuple.expressions.get(1) {
26642                                self.write(" ");
26643                                self.generate_expression(v)?;
26644                            }
26645                        } else {
26646                            self.generate_expression(pair)?;
26647                        }
26648                    }
26649                }
26650            } else {
26651                self.generate_expression(settings)?;
26652            }
26653            self.write(")");
26654        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26655            self.write("()");
26656        }
26657        self.write(")");
26658        Ok(())
26659    }
26660
26661    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26662        let property_name = match e.this.as_ref() {
26663            Expression::Identifier(id) => id.name.as_str(),
26664            Expression::Var(v) => v.this.as_str(),
26665            _ => "RANGE",
26666        };
26667        self.write_keyword(property_name);
26668        self.write("(");
26669        if let Some(min) = &e.min {
26670            self.write_keyword("MIN");
26671            self.write_space();
26672            self.generate_expression(min)?;
26673        }
26674        if let Some(max) = &e.max {
26675            self.write_space();
26676            self.write_keyword("MAX");
26677            self.write_space();
26678            self.generate_expression(max)?;
26679        }
26680        self.write(")");
26681        Ok(())
26682    }
26683
26684    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26685        // Python: {local}DIRECTORY {this}{row_format}
26686        if e.local.is_some() {
26687            self.write_keyword("LOCAL ");
26688        }
26689        self.write_keyword("DIRECTORY");
26690        self.write_space();
26691        self.generate_expression(&e.this)?;
26692        if let Some(row_format) = &e.row_format {
26693            self.write_space();
26694            self.generate_expression(row_format)?;
26695        }
26696        Ok(())
26697    }
26698
26699    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26700        // Redshift: DISTKEY(column)
26701        self.write_keyword("DISTKEY");
26702        self.write("(");
26703        self.generate_expression(&e.this)?;
26704        self.write(")");
26705        Ok(())
26706    }
26707
26708    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26709        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26710        self.write_keyword("DISTSTYLE");
26711        self.write_space();
26712        self.generate_expression(&e.this)?;
26713        Ok(())
26714    }
26715
26716    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26717        // Python: "DISTRIBUTE BY" expressions
26718        self.write_keyword("DISTRIBUTE BY");
26719        self.write_space();
26720        for (i, expr) in e.expressions.iter().enumerate() {
26721            if i > 0 {
26722                self.write(", ");
26723            }
26724            self.generate_expression(expr)?;
26725        }
26726        Ok(())
26727    }
26728
26729    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26730        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26731        self.write_keyword("DISTRIBUTED BY");
26732        self.write_space();
26733        self.write(&e.kind);
26734        if !e.expressions.is_empty() {
26735            self.write(" (");
26736            for (i, expr) in e.expressions.iter().enumerate() {
26737                if i > 0 {
26738                    self.write(", ");
26739                }
26740                self.generate_expression(expr)?;
26741            }
26742            self.write(")");
26743        }
26744        if let Some(buckets) = &e.buckets {
26745            self.write_space();
26746            self.write_keyword("BUCKETS");
26747            self.write_space();
26748            self.generate_expression(buckets)?;
26749        }
26750        if let Some(order) = &e.order {
26751            self.write_space();
26752            self.generate_expression(order)?;
26753        }
26754        Ok(())
26755    }
26756
26757    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26758        // DOT_PRODUCT(vector1, vector2)
26759        self.write_keyword("DOT_PRODUCT");
26760        self.write("(");
26761        self.generate_expression(&e.this)?;
26762        self.write(", ");
26763        self.generate_expression(&e.expression)?;
26764        self.write(")");
26765        Ok(())
26766    }
26767
26768    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26769        // Python: DROP{IF EXISTS }expressions
26770        self.write_keyword("DROP");
26771        if e.exists {
26772            self.write_keyword(" IF EXISTS ");
26773        } else {
26774            self.write_space();
26775        }
26776        for (i, expr) in e.expressions.iter().enumerate() {
26777            if i > 0 {
26778                self.write(", ");
26779            }
26780            self.generate_expression(expr)?;
26781        }
26782        Ok(())
26783    }
26784
26785    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26786        // Python: DUPLICATE KEY (expressions)
26787        self.write_keyword("DUPLICATE KEY");
26788        self.write(" (");
26789        for (i, expr) in e.expressions.iter().enumerate() {
26790            if i > 0 {
26791                self.write(", ");
26792            }
26793            self.generate_expression(expr)?;
26794        }
26795        self.write(")");
26796        Ok(())
26797    }
26798
26799    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26800        // ELT(index, str1, str2, ...)
26801        self.write_keyword("ELT");
26802        self.write("(");
26803        self.generate_expression(&e.this)?;
26804        for expr in &e.expressions {
26805            self.write(", ");
26806            self.generate_expression(expr)?;
26807        }
26808        self.write(")");
26809        Ok(())
26810    }
26811
26812    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26813        // ENCODE(string, charset)
26814        self.write_keyword("ENCODE");
26815        self.write("(");
26816        self.generate_expression(&e.this)?;
26817        if let Some(charset) = &e.charset {
26818            self.write(", ");
26819            self.generate_expression(charset)?;
26820        }
26821        self.write(")");
26822        Ok(())
26823    }
26824
26825    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26826        // Python: [KEY ]ENCODE this [properties]
26827        if e.key.is_some() {
26828            self.write_keyword("KEY ");
26829        }
26830        self.write_keyword("ENCODE");
26831        self.write_space();
26832        self.generate_expression(&e.this)?;
26833        if !e.properties.is_empty() {
26834            self.write(" (");
26835            for (i, prop) in e.properties.iter().enumerate() {
26836                if i > 0 {
26837                    self.write(", ");
26838                }
26839                self.generate_expression(prop)?;
26840            }
26841            self.write(")");
26842        }
26843        Ok(())
26844    }
26845
26846    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26847        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26848        self.write_keyword("ENCRYPT");
26849        self.write("(");
26850        self.generate_expression(&e.this)?;
26851        if let Some(passphrase) = &e.passphrase {
26852            self.write(", ");
26853            self.generate_expression(passphrase)?;
26854        }
26855        if let Some(aad) = &e.aad {
26856            self.write(", ");
26857            self.generate_expression(aad)?;
26858        }
26859        if let Some(method) = &e.encryption_method {
26860            self.write(", ");
26861            self.generate_expression(method)?;
26862        }
26863        self.write(")");
26864        Ok(())
26865    }
26866
26867    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26868        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26869        self.write_keyword("ENCRYPT_RAW");
26870        self.write("(");
26871        self.generate_expression(&e.this)?;
26872        if let Some(key) = &e.key {
26873            self.write(", ");
26874            self.generate_expression(key)?;
26875        }
26876        if let Some(iv) = &e.iv {
26877            self.write(", ");
26878            self.generate_expression(iv)?;
26879        }
26880        if let Some(aad) = &e.aad {
26881            self.write(", ");
26882            self.generate_expression(aad)?;
26883        }
26884        if let Some(method) = &e.encryption_method {
26885            self.write(", ");
26886            self.generate_expression(method)?;
26887        }
26888        self.write(")");
26889        Ok(())
26890    }
26891
26892    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26893        // MySQL: ENGINE = InnoDB
26894        self.write_keyword("ENGINE");
26895        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26896            self.write("=");
26897        } else {
26898            self.write(" = ");
26899        }
26900        self.generate_expression(&e.this)?;
26901        Ok(())
26902    }
26903
26904    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26905        // ENVIRONMENT (expressions)
26906        self.write_keyword("ENVIRONMENT");
26907        self.write(" (");
26908        for (i, expr) in e.expressions.iter().enumerate() {
26909            if i > 0 {
26910                self.write(", ");
26911            }
26912            self.generate_expression(expr)?;
26913        }
26914        self.write(")");
26915        Ok(())
26916    }
26917
26918    fn generate_ephemeral_column_constraint(
26919        &mut self,
26920        e: &EphemeralColumnConstraint,
26921    ) -> Result<()> {
26922        // MySQL: EPHEMERAL [expr]
26923        self.write_keyword("EPHEMERAL");
26924        if let Some(this) = &e.this {
26925            self.write_space();
26926            self.generate_expression(this)?;
26927        }
26928        Ok(())
26929    }
26930
26931    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26932        // Snowflake: EQUAL_NULL(a, b)
26933        self.write_keyword("EQUAL_NULL");
26934        self.write("(");
26935        self.generate_expression(&e.this)?;
26936        self.write(", ");
26937        self.generate_expression(&e.expression)?;
26938        self.write(")");
26939        Ok(())
26940    }
26941
26942    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26943        use crate::dialects::DialectType;
26944
26945        // PostgreSQL uses <-> operator syntax
26946        match self.config.dialect {
26947            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26948                self.generate_expression(&e.this)?;
26949                self.write(" <-> ");
26950                self.generate_expression(&e.expression)?;
26951            }
26952            _ => {
26953                // Other dialects use EUCLIDEAN_DISTANCE function
26954                self.write_keyword("EUCLIDEAN_DISTANCE");
26955                self.write("(");
26956                self.generate_expression(&e.this)?;
26957                self.write(", ");
26958                self.generate_expression(&e.expression)?;
26959                self.write(")");
26960            }
26961        }
26962        Ok(())
26963    }
26964
26965    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26966        // EXECUTE AS CALLER|OWNER|user
26967        self.write_keyword("EXECUTE AS");
26968        self.write_space();
26969        self.generate_expression(&e.this)?;
26970        Ok(())
26971    }
26972
26973    fn generate_export(&mut self, e: &Export) -> Result<()> {
26974        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26975        self.write_keyword("EXPORT DATA");
26976        if let Some(connection) = &e.connection {
26977            self.write_space();
26978            self.write_keyword("WITH CONNECTION");
26979            self.write_space();
26980            self.generate_expression(connection)?;
26981        }
26982        if !e.options.is_empty() {
26983            self.write_space();
26984            self.generate_options_clause(&e.options)?;
26985        }
26986        self.write_space();
26987        self.write_keyword("AS");
26988        self.write_space();
26989        self.generate_expression(&e.this)?;
26990        Ok(())
26991    }
26992
26993    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26994        // EXTERNAL [this]
26995        self.write_keyword("EXTERNAL");
26996        if let Some(this) = &e.this {
26997            self.write_space();
26998            self.generate_expression(this)?;
26999        }
27000        Ok(())
27001    }
27002
27003    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
27004        // Python: {no}FALLBACK{protection}
27005        if e.no.is_some() {
27006            self.write_keyword("NO ");
27007        }
27008        self.write_keyword("FALLBACK");
27009        if e.protection.is_some() {
27010            self.write_keyword(" PROTECTION");
27011        }
27012        Ok(())
27013    }
27014
27015    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
27016        // BigQuery: FARM_FINGERPRINT(value)
27017        self.write_keyword("FARM_FINGERPRINT");
27018        self.write("(");
27019        for (i, expr) in e.expressions.iter().enumerate() {
27020            if i > 0 {
27021                self.write(", ");
27022            }
27023            self.generate_expression(expr)?;
27024        }
27025        self.write(")");
27026        Ok(())
27027    }
27028
27029    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
27030        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
27031        self.write_keyword("FEATURES_AT_TIME");
27032        self.write("(");
27033        self.generate_expression(&e.this)?;
27034        if let Some(time) = &e.time {
27035            self.write(", ");
27036            self.generate_expression(time)?;
27037        }
27038        if let Some(num_rows) = &e.num_rows {
27039            self.write(", ");
27040            self.generate_expression(num_rows)?;
27041        }
27042        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
27043            self.write(", ");
27044            self.generate_expression(ignore_nulls)?;
27045        }
27046        self.write(")");
27047        Ok(())
27048    }
27049
27050    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
27051        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
27052        let use_limit = !e.percent
27053            && !e.with_ties
27054            && e.count.is_some()
27055            && matches!(
27056                self.config.dialect,
27057                Some(DialectType::Spark)
27058                    | Some(DialectType::Hive)
27059                    | Some(DialectType::DuckDB)
27060                    | Some(DialectType::SQLite)
27061                    | Some(DialectType::MySQL)
27062                    | Some(DialectType::BigQuery)
27063                    | Some(DialectType::Databricks)
27064                    | Some(DialectType::StarRocks)
27065                    | Some(DialectType::Doris)
27066                    | Some(DialectType::Athena)
27067                    | Some(DialectType::ClickHouse)
27068            );
27069
27070        if use_limit {
27071            self.write_keyword("LIMIT");
27072            self.write_space();
27073            self.generate_expression(e.count.as_ref().unwrap())?;
27074            return Ok(());
27075        }
27076
27077        // Python: FETCH direction count limit_options
27078        self.write_keyword("FETCH");
27079        if !e.direction.is_empty() {
27080            self.write_space();
27081            self.write_keyword(&e.direction);
27082        }
27083        if let Some(count) = &e.count {
27084            self.write_space();
27085            self.generate_expression(count)?;
27086        }
27087        // Generate PERCENT, ROWS, WITH TIES/ONLY
27088        if e.percent {
27089            self.write_keyword(" PERCENT");
27090        }
27091        if e.rows {
27092            self.write_keyword(" ROWS");
27093        }
27094        if e.with_ties {
27095            self.write_keyword(" WITH TIES");
27096        } else if e.rows {
27097            self.write_keyword(" ONLY");
27098        } else {
27099            self.write_keyword(" ROWS ONLY");
27100        }
27101        Ok(())
27102    }
27103
27104    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
27105        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
27106        // For Spark/Databricks without hive_format: USING this
27107        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
27108        if e.hive_format.is_some() {
27109            // Hive format: STORED AS ...
27110            self.write_keyword("STORED AS");
27111            self.write_space();
27112            if let Some(this) = &e.this {
27113                // Uppercase the format name (e.g., parquet -> PARQUET)
27114                if let Expression::Identifier(id) = this.as_ref() {
27115                    self.write_keyword(&id.name.to_uppercase());
27116                } else {
27117                    self.generate_expression(this)?;
27118                }
27119            }
27120        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
27121            // Hive: STORED AS format
27122            self.write_keyword("STORED AS");
27123            self.write_space();
27124            if let Some(this) = &e.this {
27125                if let Expression::Identifier(id) = this.as_ref() {
27126                    self.write_keyword(&id.name.to_uppercase());
27127                } else {
27128                    self.generate_expression(this)?;
27129                }
27130            }
27131        } else if matches!(
27132            self.config.dialect,
27133            Some(DialectType::Spark) | Some(DialectType::Databricks)
27134        ) {
27135            // Spark/Databricks: USING format (e.g., USING DELTA)
27136            self.write_keyword("USING");
27137            self.write_space();
27138            if let Some(this) = &e.this {
27139                self.generate_expression(this)?;
27140            }
27141        } else {
27142            // Snowflake/standard format
27143            self.write_keyword("FILE_FORMAT");
27144            self.write(" = ");
27145            if let Some(this) = &e.this {
27146                self.generate_expression(this)?;
27147            } else if !e.expressions.is_empty() {
27148                self.write("(");
27149                for (i, expr) in e.expressions.iter().enumerate() {
27150                    if i > 0 {
27151                        self.write(", ");
27152                    }
27153                    self.generate_expression(expr)?;
27154                }
27155                self.write(")");
27156            }
27157        }
27158        Ok(())
27159    }
27160
27161    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27162        // agg_func FILTER(WHERE condition)
27163        self.generate_expression(&e.this)?;
27164        self.write_space();
27165        self.write_keyword("FILTER");
27166        self.write("(");
27167        self.write_keyword("WHERE");
27168        self.write_space();
27169        self.generate_expression(&e.expression)?;
27170        self.write(")");
27171        Ok(())
27172    }
27173
27174    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27175        // FLOAT64(this) or FLOAT64(this, expression)
27176        self.write_keyword("FLOAT64");
27177        self.write("(");
27178        self.generate_expression(&e.this)?;
27179        if let Some(expr) = &e.expression {
27180            self.write(", ");
27181            self.generate_expression(expr)?;
27182        }
27183        self.write(")");
27184        Ok(())
27185    }
27186
27187    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27188        // FOR this DO expression
27189        self.write_keyword("FOR");
27190        self.write_space();
27191        self.generate_expression(&e.this)?;
27192        self.write_space();
27193        self.write_keyword("DO");
27194        self.write_space();
27195        self.generate_expression(&e.expression)?;
27196        Ok(())
27197    }
27198
27199    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27200        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27201        self.write_keyword("FOREIGN KEY");
27202        if !e.expressions.is_empty() {
27203            self.write(" (");
27204            for (i, expr) in e.expressions.iter().enumerate() {
27205                if i > 0 {
27206                    self.write(", ");
27207                }
27208                self.generate_expression(expr)?;
27209            }
27210            self.write(")");
27211        }
27212        if let Some(reference) = &e.reference {
27213            self.write_space();
27214            self.generate_expression(reference)?;
27215        }
27216        if let Some(delete) = &e.delete {
27217            self.write_space();
27218            self.write_keyword("ON DELETE");
27219            self.write_space();
27220            self.generate_expression(delete)?;
27221        }
27222        if let Some(update) = &e.update {
27223            self.write_space();
27224            self.write_keyword("ON UPDATE");
27225            self.write_space();
27226            self.generate_expression(update)?;
27227        }
27228        if !e.options.is_empty() {
27229            self.write_space();
27230            for (i, opt) in e.options.iter().enumerate() {
27231                if i > 0 {
27232                    self.write_space();
27233                }
27234                self.generate_expression(opt)?;
27235            }
27236        }
27237        Ok(())
27238    }
27239
27240    fn generate_format(&mut self, e: &Format) -> Result<()> {
27241        // FORMAT(this, expressions...)
27242        self.write_keyword("FORMAT");
27243        self.write("(");
27244        self.generate_expression(&e.this)?;
27245        for expr in &e.expressions {
27246            self.write(", ");
27247            self.generate_expression(expr)?;
27248        }
27249        self.write(")");
27250        Ok(())
27251    }
27252
27253    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27254        // Teradata: column (FORMAT 'format_string')
27255        self.generate_expression(&e.this)?;
27256        self.write(" (");
27257        self.write_keyword("FORMAT");
27258        self.write(" '");
27259        self.write(&e.format);
27260        self.write("')");
27261        Ok(())
27262    }
27263
27264    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27265        // Python: FREESPACE=this[PERCENT]
27266        self.write_keyword("FREESPACE");
27267        self.write("=");
27268        self.generate_expression(&e.this)?;
27269        if e.percent.is_some() {
27270            self.write_keyword(" PERCENT");
27271        }
27272        Ok(())
27273    }
27274
27275    fn generate_from(&mut self, e: &From) -> Result<()> {
27276        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27277        self.write_keyword("FROM");
27278        self.write_space();
27279
27280        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27281        // But keep commas when TABLESAMPLE is present
27282        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27283        use crate::dialects::DialectType;
27284        let has_tablesample = e
27285            .expressions
27286            .iter()
27287            .any(|expr| matches!(expr, Expression::TableSample(_)));
27288        let is_cross_join_dialect = matches!(
27289            self.config.dialect,
27290            Some(DialectType::BigQuery)
27291                | Some(DialectType::Hive)
27292                | Some(DialectType::Spark)
27293                | Some(DialectType::Databricks)
27294                | Some(DialectType::SQLite)
27295                | Some(DialectType::ClickHouse)
27296        );
27297        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27298            && self.config.source_dialect == self.config.dialect;
27299        let source_is_cross_join_dialect2 = matches!(
27300            self.config.source_dialect,
27301            Some(DialectType::BigQuery)
27302                | Some(DialectType::Hive)
27303                | Some(DialectType::Spark)
27304                | Some(DialectType::Databricks)
27305                | Some(DialectType::SQLite)
27306                | Some(DialectType::ClickHouse)
27307        );
27308        let use_cross_join = !has_tablesample
27309            && is_cross_join_dialect
27310            && (source_is_same_as_target2
27311                || source_is_cross_join_dialect2
27312                || self.config.source_dialect.is_none());
27313
27314        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27315        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27316
27317        for (i, expr) in e.expressions.iter().enumerate() {
27318            if i > 0 {
27319                if use_cross_join {
27320                    self.write(" CROSS JOIN ");
27321                } else {
27322                    self.write(", ");
27323                }
27324            }
27325            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27326                self.write("(");
27327                self.generate_expression(expr)?;
27328                self.write(")");
27329            } else {
27330                self.generate_expression(expr)?;
27331            }
27332        }
27333        Ok(())
27334    }
27335
27336    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27337        // FROM_BASE(this, expression) - convert from base N
27338        self.write_keyword("FROM_BASE");
27339        self.write("(");
27340        self.generate_expression(&e.this)?;
27341        self.write(", ");
27342        self.generate_expression(&e.expression)?;
27343        self.write(")");
27344        Ok(())
27345    }
27346
27347    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27348        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27349        self.generate_expression(&e.this)?;
27350        if let Some(zone) = &e.zone {
27351            self.write_space();
27352            self.write_keyword("AT TIME ZONE");
27353            self.write_space();
27354            self.generate_expression(zone)?;
27355            self.write_space();
27356            self.write_keyword("AT TIME ZONE");
27357            self.write(" 'UTC'");
27358        }
27359        Ok(())
27360    }
27361
27362    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27363        // GAP_FILL(this, ts_column, bucket_width, ...)
27364        self.write_keyword("GAP_FILL");
27365        self.write("(");
27366        self.generate_expression(&e.this)?;
27367        if let Some(ts_column) = &e.ts_column {
27368            self.write(", ");
27369            self.generate_expression(ts_column)?;
27370        }
27371        if let Some(bucket_width) = &e.bucket_width {
27372            self.write(", ");
27373            self.generate_expression(bucket_width)?;
27374        }
27375        if let Some(partitioning_columns) = &e.partitioning_columns {
27376            self.write(", ");
27377            self.generate_expression(partitioning_columns)?;
27378        }
27379        if let Some(value_columns) = &e.value_columns {
27380            self.write(", ");
27381            self.generate_expression(value_columns)?;
27382        }
27383        self.write(")");
27384        Ok(())
27385    }
27386
27387    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27388        // GENERATE_DATE_ARRAY(start, end, step)
27389        self.write_keyword("GENERATE_DATE_ARRAY");
27390        self.write("(");
27391        let mut first = true;
27392        if let Some(start) = &e.start {
27393            self.generate_expression(start)?;
27394            first = false;
27395        }
27396        if let Some(end) = &e.end {
27397            if !first {
27398                self.write(", ");
27399            }
27400            self.generate_expression(end)?;
27401            first = false;
27402        }
27403        if let Some(step) = &e.step {
27404            if !first {
27405                self.write(", ");
27406            }
27407            self.generate_expression(step)?;
27408        }
27409        self.write(")");
27410        Ok(())
27411    }
27412
27413    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27414        // ML.GENERATE_EMBEDDING(model, content, params)
27415        self.write_keyword("ML.GENERATE_EMBEDDING");
27416        self.write("(");
27417        self.generate_expression(&e.this)?;
27418        self.write(", ");
27419        self.generate_expression(&e.expression)?;
27420        if let Some(params) = &e.params_struct {
27421            self.write(", ");
27422            self.generate_expression(params)?;
27423        }
27424        self.write(")");
27425        Ok(())
27426    }
27427
27428    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27429        // Dialect-specific function name
27430        let fn_name = match self.config.dialect {
27431            Some(DialectType::Presto)
27432            | Some(DialectType::Trino)
27433            | Some(DialectType::Athena)
27434            | Some(DialectType::Spark)
27435            | Some(DialectType::Databricks)
27436            | Some(DialectType::Hive) => "SEQUENCE",
27437            _ => "GENERATE_SERIES",
27438        };
27439        self.write_keyword(fn_name);
27440        self.write("(");
27441        let mut first = true;
27442        if let Some(start) = &e.start {
27443            self.generate_expression(start)?;
27444            first = false;
27445        }
27446        if let Some(end) = &e.end {
27447            if !first {
27448                self.write(", ");
27449            }
27450            self.generate_expression(end)?;
27451            first = false;
27452        }
27453        if let Some(step) = &e.step {
27454            if !first {
27455                self.write(", ");
27456            }
27457            // For Presto/Trino: convert WEEK intervals to DAY multiples
27458            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27459            if matches!(
27460                self.config.dialect,
27461                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27462            ) {
27463                if let Some(converted) = self.convert_week_interval_to_day(step) {
27464                    self.generate_expression(&converted)?;
27465                } else {
27466                    self.generate_expression(step)?;
27467                }
27468            } else {
27469                self.generate_expression(step)?;
27470            }
27471        }
27472        self.write(")");
27473        Ok(())
27474    }
27475
27476    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27477    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27478    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27479        use crate::expressions::*;
27480        if let Expression::Interval(ref iv) = expr {
27481            // Check for structured WEEK unit
27482            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27483                unit: IntervalUnit::Week,
27484                ..
27485            }) = &iv.unit
27486            {
27487                // Value is in iv.this
27488                let count = match &iv.this {
27489                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27490                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27491                    _ => return None,
27492                };
27493                (true, count)
27494            } else if iv.unit.is_none() {
27495                // Check for string-encoded interval like "1 WEEK"
27496                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27497                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27498                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27499                        (true, parts[0].to_string())
27500                    } else {
27501                        (false, String::new())
27502                    }
27503                } else {
27504                    (false, String::new())
27505                }
27506            } else {
27507                (false, String::new())
27508            };
27509
27510            if is_week {
27511                // Build: (N * INTERVAL '7' DAY)
27512                let count_expr = Expression::Literal(Literal::Number(count_str));
27513                let day_interval = Expression::Interval(Box::new(Interval {
27514                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27515                    unit: Some(IntervalUnitSpec::Simple {
27516                        unit: IntervalUnit::Day,
27517                        use_plural: false,
27518                    }),
27519                }));
27520                let mul = Expression::Mul(Box::new(BinaryOp {
27521                    left: count_expr,
27522                    right: day_interval,
27523                    left_comments: vec![],
27524                    operator_comments: vec![],
27525                    trailing_comments: vec![],
27526                    inferred_type: None,
27527                }));
27528                return Some(Expression::Paren(Box::new(Paren {
27529                    this: mul,
27530                    trailing_comments: vec![],
27531                })));
27532            }
27533        }
27534        None
27535    }
27536
27537    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27538        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27539        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27540        self.write("(");
27541        let mut first = true;
27542        if let Some(start) = &e.start {
27543            self.generate_expression(start)?;
27544            first = false;
27545        }
27546        if let Some(end) = &e.end {
27547            if !first {
27548                self.write(", ");
27549            }
27550            self.generate_expression(end)?;
27551            first = false;
27552        }
27553        if let Some(step) = &e.step {
27554            if !first {
27555                self.write(", ");
27556            }
27557            self.generate_expression(step)?;
27558        }
27559        self.write(")");
27560        Ok(())
27561    }
27562
27563    fn generate_generated_as_identity_column_constraint(
27564        &mut self,
27565        e: &GeneratedAsIdentityColumnConstraint,
27566    ) -> Result<()> {
27567        use crate::dialects::DialectType;
27568
27569        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27570        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27571            self.write_keyword("AUTOINCREMENT");
27572            if let Some(start) = &e.start {
27573                self.write_keyword(" START ");
27574                self.generate_expression(start)?;
27575            }
27576            if let Some(increment) = &e.increment {
27577                self.write_keyword(" INCREMENT ");
27578                self.generate_expression(increment)?;
27579            }
27580            return Ok(());
27581        }
27582
27583        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27584        self.write_keyword("GENERATED");
27585        if let Some(this) = &e.this {
27586            // Check if it's a truthy boolean expression
27587            if let Expression::Boolean(b) = this.as_ref() {
27588                if b.value {
27589                    self.write_keyword(" ALWAYS");
27590                } else {
27591                    self.write_keyword(" BY DEFAULT");
27592                    if e.on_null.is_some() {
27593                        self.write_keyword(" ON NULL");
27594                    }
27595                }
27596            } else {
27597                self.write_keyword(" ALWAYS");
27598            }
27599        }
27600        self.write_keyword(" AS IDENTITY");
27601        // Add sequence options if any
27602        let has_options = e.start.is_some()
27603            || e.increment.is_some()
27604            || e.minvalue.is_some()
27605            || e.maxvalue.is_some();
27606        if has_options {
27607            self.write(" (");
27608            let mut first = true;
27609            if let Some(start) = &e.start {
27610                self.write_keyword("START WITH ");
27611                self.generate_expression(start)?;
27612                first = false;
27613            }
27614            if let Some(increment) = &e.increment {
27615                if !first {
27616                    self.write(" ");
27617                }
27618                self.write_keyword("INCREMENT BY ");
27619                self.generate_expression(increment)?;
27620                first = false;
27621            }
27622            if let Some(minvalue) = &e.minvalue {
27623                if !first {
27624                    self.write(" ");
27625                }
27626                self.write_keyword("MINVALUE ");
27627                self.generate_expression(minvalue)?;
27628                first = false;
27629            }
27630            if let Some(maxvalue) = &e.maxvalue {
27631                if !first {
27632                    self.write(" ");
27633                }
27634                self.write_keyword("MAXVALUE ");
27635                self.generate_expression(maxvalue)?;
27636            }
27637            self.write(")");
27638        }
27639        Ok(())
27640    }
27641
27642    fn generate_generated_as_row_column_constraint(
27643        &mut self,
27644        e: &GeneratedAsRowColumnConstraint,
27645    ) -> Result<()> {
27646        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27647        self.write_keyword("GENERATED ALWAYS AS ROW ");
27648        if e.start.is_some() {
27649            self.write_keyword("START");
27650        } else {
27651            self.write_keyword("END");
27652        }
27653        if e.hidden.is_some() {
27654            self.write_keyword(" HIDDEN");
27655        }
27656        Ok(())
27657    }
27658
27659    fn generate_get(&mut self, e: &Get) -> Result<()> {
27660        // GET this target properties
27661        self.write_keyword("GET");
27662        self.write_space();
27663        self.generate_expression(&e.this)?;
27664        if let Some(target) = &e.target {
27665            self.write_space();
27666            self.generate_expression(target)?;
27667        }
27668        for prop in &e.properties {
27669            self.write_space();
27670            self.generate_expression(prop)?;
27671        }
27672        Ok(())
27673    }
27674
27675    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27676        // GetExtract generates bracket access: this[expression]
27677        self.generate_expression(&e.this)?;
27678        self.write("[");
27679        self.generate_expression(&e.expression)?;
27680        self.write("]");
27681        Ok(())
27682    }
27683
27684    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27685        // GETBIT(this, expression) or GET_BIT(this, expression)
27686        self.write_keyword("GETBIT");
27687        self.write("(");
27688        self.generate_expression(&e.this)?;
27689        self.write(", ");
27690        self.generate_expression(&e.expression)?;
27691        self.write(")");
27692        Ok(())
27693    }
27694
27695    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27696        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27697        if e.is_role {
27698            self.write_keyword("ROLE");
27699            self.write_space();
27700        } else if e.is_group {
27701            self.write_keyword("GROUP");
27702            self.write_space();
27703        }
27704        self.write(&e.name.name);
27705        Ok(())
27706    }
27707
27708    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27709        // privilege(columns) or just privilege
27710        self.generate_expression(&e.this)?;
27711        if !e.expressions.is_empty() {
27712            self.write("(");
27713            for (i, expr) in e.expressions.iter().enumerate() {
27714                if i > 0 {
27715                    self.write(", ");
27716                }
27717                self.generate_expression(expr)?;
27718            }
27719            self.write(")");
27720        }
27721        Ok(())
27722    }
27723
27724    fn generate_group(&mut self, e: &Group) -> Result<()> {
27725        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27726        self.write_keyword("GROUP BY");
27727        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27728        match e.all {
27729            Some(true) => {
27730                self.write_space();
27731                self.write_keyword("ALL");
27732            }
27733            Some(false) => {
27734                self.write_space();
27735                self.write_keyword("DISTINCT");
27736            }
27737            None => {}
27738        }
27739        if !e.expressions.is_empty() {
27740            self.write_space();
27741            for (i, expr) in e.expressions.iter().enumerate() {
27742                if i > 0 {
27743                    self.write(", ");
27744                }
27745                self.generate_expression(expr)?;
27746            }
27747        }
27748        // Handle CUBE, ROLLUP, GROUPING SETS
27749        if let Some(cube) = &e.cube {
27750            if !e.expressions.is_empty() {
27751                self.write(", ");
27752            } else {
27753                self.write_space();
27754            }
27755            self.generate_expression(cube)?;
27756        }
27757        if let Some(rollup) = &e.rollup {
27758            if !e.expressions.is_empty() || e.cube.is_some() {
27759                self.write(", ");
27760            } else {
27761                self.write_space();
27762            }
27763            self.generate_expression(rollup)?;
27764        }
27765        if let Some(grouping_sets) = &e.grouping_sets {
27766            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27767                self.write(", ");
27768            } else {
27769                self.write_space();
27770            }
27771            self.generate_expression(grouping_sets)?;
27772        }
27773        if let Some(totals) = &e.totals {
27774            self.write_space();
27775            self.write_keyword("WITH TOTALS");
27776            self.generate_expression(totals)?;
27777        }
27778        Ok(())
27779    }
27780
27781    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27782        // GROUP BY expressions
27783        self.write_keyword("GROUP BY");
27784        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27785        match e.all {
27786            Some(true) => {
27787                self.write_space();
27788                self.write_keyword("ALL");
27789            }
27790            Some(false) => {
27791                self.write_space();
27792                self.write_keyword("DISTINCT");
27793            }
27794            None => {}
27795        }
27796
27797        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27798        // These are represented as Cube/Rollup expressions with empty expressions at the end
27799        let mut trailing_cube = false;
27800        let mut trailing_rollup = false;
27801        let mut regular_expressions: Vec<&Expression> = Vec::new();
27802
27803        for expr in &e.expressions {
27804            match expr {
27805                Expression::Cube(c) if c.expressions.is_empty() => {
27806                    trailing_cube = true;
27807                }
27808                Expression::Rollup(r) if r.expressions.is_empty() => {
27809                    trailing_rollup = true;
27810                }
27811                _ => {
27812                    regular_expressions.push(expr);
27813                }
27814            }
27815        }
27816
27817        // In pretty mode, put columns on separate lines
27818        if self.config.pretty {
27819            self.write_newline();
27820            self.indent_level += 1;
27821            for (i, expr) in regular_expressions.iter().enumerate() {
27822                if i > 0 {
27823                    self.write(",");
27824                    self.write_newline();
27825                }
27826                self.write_indent();
27827                self.generate_expression(expr)?;
27828            }
27829            self.indent_level -= 1;
27830        } else {
27831            self.write_space();
27832            for (i, expr) in regular_expressions.iter().enumerate() {
27833                if i > 0 {
27834                    self.write(", ");
27835                }
27836                self.generate_expression(expr)?;
27837            }
27838        }
27839
27840        // Output trailing WITH CUBE or WITH ROLLUP
27841        if trailing_cube {
27842            self.write_space();
27843            self.write_keyword("WITH CUBE");
27844        } else if trailing_rollup {
27845            self.write_space();
27846            self.write_keyword("WITH ROLLUP");
27847        }
27848
27849        // ClickHouse: WITH TOTALS
27850        if e.totals {
27851            self.write_space();
27852            self.write_keyword("WITH TOTALS");
27853        }
27854
27855        Ok(())
27856    }
27857
27858    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27859        // GROUPING(col1, col2, ...)
27860        self.write_keyword("GROUPING");
27861        self.write("(");
27862        for (i, expr) in e.expressions.iter().enumerate() {
27863            if i > 0 {
27864                self.write(", ");
27865            }
27866            self.generate_expression(expr)?;
27867        }
27868        self.write(")");
27869        Ok(())
27870    }
27871
27872    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27873        // GROUPING_ID(col1, col2, ...)
27874        self.write_keyword("GROUPING_ID");
27875        self.write("(");
27876        for (i, expr) in e.expressions.iter().enumerate() {
27877            if i > 0 {
27878                self.write(", ");
27879            }
27880            self.generate_expression(expr)?;
27881        }
27882        self.write(")");
27883        Ok(())
27884    }
27885
27886    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27887        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27888        self.write_keyword("GROUPING SETS");
27889        self.write(" (");
27890        for (i, expr) in e.expressions.iter().enumerate() {
27891            if i > 0 {
27892                self.write(", ");
27893            }
27894            self.generate_expression(expr)?;
27895        }
27896        self.write(")");
27897        Ok(())
27898    }
27899
27900    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27901        // HASH_AGG(this, expressions...)
27902        self.write_keyword("HASH_AGG");
27903        self.write("(");
27904        self.generate_expression(&e.this)?;
27905        for expr in &e.expressions {
27906            self.write(", ");
27907            self.generate_expression(expr)?;
27908        }
27909        self.write(")");
27910        Ok(())
27911    }
27912
27913    fn generate_having(&mut self, e: &Having) -> Result<()> {
27914        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27915        self.write_keyword("HAVING");
27916        self.write_space();
27917        self.generate_expression(&e.this)?;
27918        Ok(())
27919    }
27920
27921    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27922        // Python: this HAVING MAX|MIN expression
27923        self.generate_expression(&e.this)?;
27924        self.write_space();
27925        self.write_keyword("HAVING");
27926        self.write_space();
27927        if e.max.is_some() {
27928            self.write_keyword("MAX");
27929        } else {
27930            self.write_keyword("MIN");
27931        }
27932        self.write_space();
27933        self.generate_expression(&e.expression)?;
27934        Ok(())
27935    }
27936
27937    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27938        use crate::dialects::DialectType;
27939        // DuckDB: convert dollar-tagged strings to single-quoted
27940        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27941            // Extract the string content and output as single-quoted
27942            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27943                return self.generate_string_literal(s);
27944            }
27945        }
27946        // PostgreSQL: preserve dollar-quoting
27947        if matches!(
27948            self.config.dialect,
27949            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27950        ) {
27951            self.write("$");
27952            if let Some(tag) = &e.tag {
27953                self.generate_expression(tag)?;
27954            }
27955            self.write("$");
27956            self.generate_expression(&e.this)?;
27957            self.write("$");
27958            if let Some(tag) = &e.tag {
27959                self.generate_expression(tag)?;
27960            }
27961            self.write("$");
27962            return Ok(());
27963        }
27964        // Default: output as dollar-tagged
27965        self.write("$");
27966        if let Some(tag) = &e.tag {
27967            self.generate_expression(tag)?;
27968        }
27969        self.write("$");
27970        self.generate_expression(&e.this)?;
27971        self.write("$");
27972        if let Some(tag) = &e.tag {
27973            self.generate_expression(tag)?;
27974        }
27975        self.write("$");
27976        Ok(())
27977    }
27978
27979    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27980        // HEX_ENCODE(this)
27981        self.write_keyword("HEX_ENCODE");
27982        self.write("(");
27983        self.generate_expression(&e.this)?;
27984        self.write(")");
27985        Ok(())
27986    }
27987
27988    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27989        // Python: this (kind => expression)
27990        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27991        match e.this.as_ref() {
27992            Expression::Identifier(id) => self.write(&id.name),
27993            other => self.generate_expression(other)?,
27994        }
27995        self.write(" (");
27996        self.write(&e.kind);
27997        self.write(" => ");
27998        self.generate_expression(&e.expression)?;
27999        self.write(")");
28000        Ok(())
28001    }
28002
28003    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
28004        // HLL(this, expressions...)
28005        self.write_keyword("HLL");
28006        self.write("(");
28007        self.generate_expression(&e.this)?;
28008        for expr in &e.expressions {
28009            self.write(", ");
28010            self.generate_expression(expr)?;
28011        }
28012        self.write(")");
28013        Ok(())
28014    }
28015
28016    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
28017        // Python: IN|OUT|IN OUT
28018        if e.input_.is_some() && e.output.is_some() {
28019            self.write_keyword("IN OUT");
28020        } else if e.input_.is_some() {
28021            self.write_keyword("IN");
28022        } else if e.output.is_some() {
28023            self.write_keyword("OUT");
28024        }
28025        Ok(())
28026    }
28027
28028    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
28029        // Python: INCLUDE this [column_def] [AS alias]
28030        self.write_keyword("INCLUDE");
28031        self.write_space();
28032        self.generate_expression(&e.this)?;
28033        if let Some(column_def) = &e.column_def {
28034            self.write_space();
28035            self.generate_expression(column_def)?;
28036        }
28037        if let Some(alias) = &e.alias {
28038            self.write_space();
28039            self.write_keyword("AS");
28040            self.write_space();
28041            self.write(alias);
28042        }
28043        Ok(())
28044    }
28045
28046    fn generate_index(&mut self, e: &Index) -> Result<()> {
28047        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
28048        if e.unique {
28049            self.write_keyword("UNIQUE");
28050            self.write_space();
28051        }
28052        if e.primary.is_some() {
28053            self.write_keyword("PRIMARY");
28054            self.write_space();
28055        }
28056        if e.amp.is_some() {
28057            self.write_keyword("AMP");
28058            self.write_space();
28059        }
28060        if e.table.is_none() {
28061            self.write_keyword("INDEX");
28062            self.write_space();
28063        }
28064        if let Some(name) = &e.this {
28065            self.generate_expression(name)?;
28066            self.write_space();
28067        }
28068        if let Some(table) = &e.table {
28069            self.write_keyword("ON");
28070            self.write_space();
28071            self.generate_expression(table)?;
28072        }
28073        if !e.params.is_empty() {
28074            self.write("(");
28075            for (i, param) in e.params.iter().enumerate() {
28076                if i > 0 {
28077                    self.write(", ");
28078                }
28079                self.generate_expression(param)?;
28080            }
28081            self.write(")");
28082        }
28083        Ok(())
28084    }
28085
28086    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
28087        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
28088        if let Some(kind) = &e.kind {
28089            self.write(kind);
28090            self.write_space();
28091        }
28092        self.write_keyword("INDEX");
28093        if let Some(this) = &e.this {
28094            self.write_space();
28095            self.generate_expression(this)?;
28096        }
28097        if let Some(index_type) = &e.index_type {
28098            self.write_space();
28099            self.write_keyword("USING");
28100            self.write_space();
28101            self.generate_expression(index_type)?;
28102        }
28103        if !e.expressions.is_empty() {
28104            self.write(" (");
28105            for (i, expr) in e.expressions.iter().enumerate() {
28106                if i > 0 {
28107                    self.write(", ");
28108                }
28109                self.generate_expression(expr)?;
28110            }
28111            self.write(")");
28112        }
28113        for opt in &e.options {
28114            self.write_space();
28115            self.generate_expression(opt)?;
28116        }
28117        Ok(())
28118    }
28119
28120    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
28121        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
28122        if let Some(key_block_size) = &e.key_block_size {
28123            self.write_keyword("KEY_BLOCK_SIZE");
28124            self.write(" = ");
28125            self.generate_expression(key_block_size)?;
28126        } else if let Some(using) = &e.using {
28127            self.write_keyword("USING");
28128            self.write_space();
28129            self.generate_expression(using)?;
28130        } else if let Some(parser) = &e.parser {
28131            self.write_keyword("WITH PARSER");
28132            self.write_space();
28133            self.generate_expression(parser)?;
28134        } else if let Some(comment) = &e.comment {
28135            self.write_keyword("COMMENT");
28136            self.write_space();
28137            self.generate_expression(comment)?;
28138        } else if let Some(visible) = &e.visible {
28139            self.generate_expression(visible)?;
28140        } else if let Some(engine_attr) = &e.engine_attr {
28141            self.write_keyword("ENGINE_ATTRIBUTE");
28142            self.write(" = ");
28143            self.generate_expression(engine_attr)?;
28144        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
28145            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
28146            self.write(" = ");
28147            self.generate_expression(secondary_engine_attr)?;
28148        }
28149        Ok(())
28150    }
28151
28152    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28153        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28154        if let Some(using) = &e.using {
28155            self.write_keyword("USING");
28156            self.write_space();
28157            self.generate_expression(using)?;
28158        }
28159        if !e.columns.is_empty() {
28160            self.write("(");
28161            for (i, col) in e.columns.iter().enumerate() {
28162                if i > 0 {
28163                    self.write(", ");
28164                }
28165                self.generate_expression(col)?;
28166            }
28167            self.write(")");
28168        }
28169        if let Some(partition_by) = &e.partition_by {
28170            self.write_space();
28171            self.write_keyword("PARTITION BY");
28172            self.write_space();
28173            self.generate_expression(partition_by)?;
28174        }
28175        if let Some(where_) = &e.where_ {
28176            self.write_space();
28177            self.generate_expression(where_)?;
28178        }
28179        if let Some(include) = &e.include {
28180            self.write_space();
28181            self.write_keyword("INCLUDE");
28182            self.write(" (");
28183            self.generate_expression(include)?;
28184            self.write(")");
28185        }
28186        if let Some(with_storage) = &e.with_storage {
28187            self.write_space();
28188            self.write_keyword("WITH");
28189            self.write(" (");
28190            self.generate_expression(with_storage)?;
28191            self.write(")");
28192        }
28193        if let Some(tablespace) = &e.tablespace {
28194            self.write_space();
28195            self.write_keyword("USING INDEX TABLESPACE");
28196            self.write_space();
28197            self.generate_expression(tablespace)?;
28198        }
28199        Ok(())
28200    }
28201
28202    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28203        // Python: this INDEX [FOR target] (expressions)
28204        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28205        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28206        if let Expression::Identifier(id) = &*e.this {
28207            self.write_keyword(&id.name);
28208        } else {
28209            self.generate_expression(&e.this)?;
28210        }
28211        self.write_space();
28212        self.write_keyword("INDEX");
28213        if let Some(target) = &e.target {
28214            self.write_space();
28215            self.write_keyword("FOR");
28216            self.write_space();
28217            if let Expression::Identifier(id) = &**target {
28218                self.write_keyword(&id.name);
28219            } else {
28220                self.generate_expression(target)?;
28221            }
28222        }
28223        // Always output parentheses (even if empty, e.g. USE INDEX ())
28224        self.write(" (");
28225        for (i, expr) in e.expressions.iter().enumerate() {
28226            if i > 0 {
28227                self.write(", ");
28228            }
28229            self.generate_expression(expr)?;
28230        }
28231        self.write(")");
28232        Ok(())
28233    }
28234
28235    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28236        // INHERITS (table1, table2, ...)
28237        self.write_keyword("INHERITS");
28238        self.write(" (");
28239        for (i, expr) in e.expressions.iter().enumerate() {
28240            if i > 0 {
28241                self.write(", ");
28242            }
28243            self.generate_expression(expr)?;
28244        }
28245        self.write(")");
28246        Ok(())
28247    }
28248
28249    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28250        // INPUT(model)
28251        self.write_keyword("INPUT");
28252        self.write("(");
28253        self.generate_expression(&e.this)?;
28254        self.write(")");
28255        Ok(())
28256    }
28257
28258    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28259        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28260        if let Some(input_format) = &e.input_format {
28261            self.write_keyword("INPUTFORMAT");
28262            self.write_space();
28263            self.generate_expression(input_format)?;
28264        }
28265        if let Some(output_format) = &e.output_format {
28266            if e.input_format.is_some() {
28267                self.write(" ");
28268            }
28269            self.write_keyword("OUTPUTFORMAT");
28270            self.write_space();
28271            self.generate_expression(output_format)?;
28272        }
28273        Ok(())
28274    }
28275
28276    fn generate_install(&mut self, e: &Install) -> Result<()> {
28277        // [FORCE] INSTALL extension [FROM source]
28278        if e.force.is_some() {
28279            self.write_keyword("FORCE");
28280            self.write_space();
28281        }
28282        self.write_keyword("INSTALL");
28283        self.write_space();
28284        self.generate_expression(&e.this)?;
28285        if let Some(from) = &e.from_ {
28286            self.write_space();
28287            self.write_keyword("FROM");
28288            self.write_space();
28289            self.generate_expression(from)?;
28290        }
28291        Ok(())
28292    }
28293
28294    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28295        // INTERVAL 'expression' unit
28296        self.write_keyword("INTERVAL");
28297        self.write_space();
28298        // When a unit is specified and the expression is a number,
28299        self.generate_expression(&e.expression)?;
28300        if let Some(unit) = &e.unit {
28301            self.write_space();
28302            self.write(unit);
28303        }
28304        Ok(())
28305    }
28306
28307    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28308        // unit TO unit (e.g., HOUR TO SECOND)
28309        self.write(&format!("{:?}", e.this).to_uppercase());
28310        self.write_space();
28311        self.write_keyword("TO");
28312        self.write_space();
28313        self.write(&format!("{:?}", e.expression).to_uppercase());
28314        Ok(())
28315    }
28316
28317    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28318        // INTO [TEMPORARY|UNLOGGED] table
28319        self.write_keyword("INTO");
28320        if e.temporary {
28321            self.write_keyword(" TEMPORARY");
28322        }
28323        if e.unlogged.is_some() {
28324            self.write_keyword(" UNLOGGED");
28325        }
28326        if let Some(this) = &e.this {
28327            self.write_space();
28328            self.generate_expression(this)?;
28329        }
28330        if !e.expressions.is_empty() {
28331            self.write(" (");
28332            for (i, expr) in e.expressions.iter().enumerate() {
28333                if i > 0 {
28334                    self.write(", ");
28335                }
28336                self.generate_expression(expr)?;
28337            }
28338            self.write(")");
28339        }
28340        Ok(())
28341    }
28342
28343    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28344        // Python: this expression (e.g., _utf8 'string')
28345        self.generate_expression(&e.this)?;
28346        self.write_space();
28347        self.generate_expression(&e.expression)?;
28348        Ok(())
28349    }
28350
28351    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28352        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28353        self.write_keyword("WITH");
28354        if e.no.is_some() {
28355            self.write_keyword(" NO");
28356        }
28357        if e.concurrent.is_some() {
28358            self.write_keyword(" CONCURRENT");
28359        }
28360        self.write_keyword(" ISOLATED LOADING");
28361        if let Some(target) = &e.target {
28362            self.write_space();
28363            self.generate_expression(target)?;
28364        }
28365        Ok(())
28366    }
28367
28368    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28369        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28370        self.write_keyword("JSON");
28371        if let Some(this) = &e.this {
28372            self.write_space();
28373            self.generate_expression(this)?;
28374        }
28375        if let Some(with_) = &e.with_ {
28376            // Check if it's a truthy boolean
28377            if let Expression::Boolean(b) = with_.as_ref() {
28378                if b.value {
28379                    self.write_keyword(" WITH");
28380                } else {
28381                    self.write_keyword(" WITHOUT");
28382                }
28383            }
28384        }
28385        if e.unique {
28386            self.write_keyword(" UNIQUE KEYS");
28387        }
28388        Ok(())
28389    }
28390
28391    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28392        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28393        self.write_keyword("JSON_ARRAY");
28394        self.write("(");
28395        for (i, expr) in e.expressions.iter().enumerate() {
28396            if i > 0 {
28397                self.write(", ");
28398            }
28399            self.generate_expression(expr)?;
28400        }
28401        if let Some(null_handling) = &e.null_handling {
28402            self.write_space();
28403            self.generate_expression(null_handling)?;
28404        }
28405        if let Some(return_type) = &e.return_type {
28406            self.write_space();
28407            self.write_keyword("RETURNING");
28408            self.write_space();
28409            self.generate_expression(return_type)?;
28410        }
28411        if e.strict.is_some() {
28412            self.write_space();
28413            self.write_keyword("STRICT");
28414        }
28415        self.write(")");
28416        Ok(())
28417    }
28418
28419    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28420        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28421        self.write_keyword("JSON_ARRAYAGG");
28422        self.write("(");
28423        self.generate_expression(&e.this)?;
28424        if let Some(order) = &e.order {
28425            self.write_space();
28426            // Order is stored as an OrderBy expression
28427            if let Expression::OrderBy(ob) = order.as_ref() {
28428                self.write_keyword("ORDER BY");
28429                self.write_space();
28430                for (i, ord) in ob.expressions.iter().enumerate() {
28431                    if i > 0 {
28432                        self.write(", ");
28433                    }
28434                    self.generate_ordered(ord)?;
28435                }
28436            } else {
28437                // Fallback: generate the expression directly
28438                self.generate_expression(order)?;
28439            }
28440        }
28441        if let Some(null_handling) = &e.null_handling {
28442            self.write_space();
28443            self.generate_expression(null_handling)?;
28444        }
28445        if let Some(return_type) = &e.return_type {
28446            self.write_space();
28447            self.write_keyword("RETURNING");
28448            self.write_space();
28449            self.generate_expression(return_type)?;
28450        }
28451        if e.strict.is_some() {
28452            self.write_space();
28453            self.write_keyword("STRICT");
28454        }
28455        self.write(")");
28456        Ok(())
28457    }
28458
28459    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28460        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28461        self.write_keyword("JSON_OBJECTAGG");
28462        self.write("(");
28463        for (i, expr) in e.expressions.iter().enumerate() {
28464            if i > 0 {
28465                self.write(", ");
28466            }
28467            self.generate_expression(expr)?;
28468        }
28469        if let Some(null_handling) = &e.null_handling {
28470            self.write_space();
28471            self.generate_expression(null_handling)?;
28472        }
28473        if let Some(unique_keys) = &e.unique_keys {
28474            self.write_space();
28475            if let Expression::Boolean(b) = unique_keys.as_ref() {
28476                if b.value {
28477                    self.write_keyword("WITH UNIQUE KEYS");
28478                } else {
28479                    self.write_keyword("WITHOUT UNIQUE KEYS");
28480                }
28481            }
28482        }
28483        if let Some(return_type) = &e.return_type {
28484            self.write_space();
28485            self.write_keyword("RETURNING");
28486            self.write_space();
28487            self.generate_expression(return_type)?;
28488        }
28489        self.write(")");
28490        Ok(())
28491    }
28492
28493    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28494        // JSON_ARRAY_APPEND(this, path, value, ...)
28495        self.write_keyword("JSON_ARRAY_APPEND");
28496        self.write("(");
28497        self.generate_expression(&e.this)?;
28498        for expr in &e.expressions {
28499            self.write(", ");
28500            self.generate_expression(expr)?;
28501        }
28502        self.write(")");
28503        Ok(())
28504    }
28505
28506    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28507        // JSON_ARRAY_CONTAINS(this, expression)
28508        self.write_keyword("JSON_ARRAY_CONTAINS");
28509        self.write("(");
28510        self.generate_expression(&e.this)?;
28511        self.write(", ");
28512        self.generate_expression(&e.expression)?;
28513        self.write(")");
28514        Ok(())
28515    }
28516
28517    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28518        // JSON_ARRAY_INSERT(this, path, value, ...)
28519        self.write_keyword("JSON_ARRAY_INSERT");
28520        self.write("(");
28521        self.generate_expression(&e.this)?;
28522        for expr in &e.expressions {
28523            self.write(", ");
28524            self.generate_expression(expr)?;
28525        }
28526        self.write(")");
28527        Ok(())
28528    }
28529
28530    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28531        // JSONB_EXISTS(this, path)
28532        self.write_keyword("JSONB_EXISTS");
28533        self.write("(");
28534        self.generate_expression(&e.this)?;
28535        if let Some(path) = &e.path {
28536            self.write(", ");
28537            self.generate_expression(path)?;
28538        }
28539        self.write(")");
28540        Ok(())
28541    }
28542
28543    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28544        // JSONB_EXTRACT_SCALAR(this, expression)
28545        self.write_keyword("JSONB_EXTRACT_SCALAR");
28546        self.write("(");
28547        self.generate_expression(&e.this)?;
28548        self.write(", ");
28549        self.generate_expression(&e.expression)?;
28550        self.write(")");
28551        Ok(())
28552    }
28553
28554    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28555        // JSONB_OBJECT_AGG(this, expression)
28556        self.write_keyword("JSONB_OBJECT_AGG");
28557        self.write("(");
28558        self.generate_expression(&e.this)?;
28559        self.write(", ");
28560        self.generate_expression(&e.expression)?;
28561        self.write(")");
28562        Ok(())
28563    }
28564
28565    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28566        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28567        if let Some(nested_schema) = &e.nested_schema {
28568            self.write_keyword("NESTED");
28569            if let Some(path) = &e.path {
28570                self.write_space();
28571                self.write_keyword("PATH");
28572                self.write_space();
28573                self.generate_expression(path)?;
28574            }
28575            self.write_space();
28576            self.generate_expression(nested_schema)?;
28577        } else {
28578            if let Some(this) = &e.this {
28579                self.generate_expression(this)?;
28580            }
28581            if let Some(kind) = &e.kind {
28582                self.write_space();
28583                self.write(kind);
28584            }
28585            if let Some(path) = &e.path {
28586                self.write_space();
28587                self.write_keyword("PATH");
28588                self.write_space();
28589                self.generate_expression(path)?;
28590            }
28591            if e.ordinality.is_some() {
28592                self.write_keyword(" FOR ORDINALITY");
28593            }
28594        }
28595        Ok(())
28596    }
28597
28598    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28599        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28600        self.write_keyword("JSON_EXISTS");
28601        self.write("(");
28602        self.generate_expression(&e.this)?;
28603        if let Some(path) = &e.path {
28604            self.write(", ");
28605            self.generate_expression(path)?;
28606        }
28607        if let Some(passing) = &e.passing {
28608            self.write_space();
28609            self.write_keyword("PASSING");
28610            self.write_space();
28611            self.generate_expression(passing)?;
28612        }
28613        if let Some(on_condition) = &e.on_condition {
28614            self.write_space();
28615            self.generate_expression(on_condition)?;
28616        }
28617        self.write(")");
28618        Ok(())
28619    }
28620
28621    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28622        self.generate_expression(&e.this)?;
28623        self.write(".:");
28624        self.generate_data_type(&e.to)?;
28625        Ok(())
28626    }
28627
28628    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28629        // JSON_EXTRACT_ARRAY(this, expression)
28630        self.write_keyword("JSON_EXTRACT_ARRAY");
28631        self.write("(");
28632        self.generate_expression(&e.this)?;
28633        if let Some(expr) = &e.expression {
28634            self.write(", ");
28635            self.generate_expression(expr)?;
28636        }
28637        self.write(")");
28638        Ok(())
28639    }
28640
28641    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28642        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28643        if let Some(option) = &e.option {
28644            self.generate_expression(option)?;
28645            self.write_space();
28646        }
28647        self.write_keyword("QUOTES");
28648        if e.scalar.is_some() {
28649            self.write_keyword(" SCALAR_ONLY");
28650        }
28651        Ok(())
28652    }
28653
28654    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28655        // JSON_EXTRACT_SCALAR(this, expression)
28656        self.write_keyword("JSON_EXTRACT_SCALAR");
28657        self.write("(");
28658        self.generate_expression(&e.this)?;
28659        self.write(", ");
28660        self.generate_expression(&e.expression)?;
28661        self.write(")");
28662        Ok(())
28663    }
28664
28665    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28666        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28667        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28668        // Otherwise output JSON_EXTRACT(this, expression)
28669        if e.variant_extract.is_some() {
28670            use crate::dialects::DialectType;
28671            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28672                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28673                self.generate_expression(&e.this)?;
28674                self.write(":");
28675                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28676                // We need to output it without quotes
28677                match e.expression.as_ref() {
28678                    Expression::Literal(Literal::String(s)) => {
28679                        self.write(s);
28680                    }
28681                    _ => {
28682                        // Fallback: generate as-is (shouldn't happen in typical cases)
28683                        self.generate_expression(&e.expression)?;
28684                    }
28685                }
28686            } else {
28687                // Snowflake and others: use GET_PATH(col, 'path')
28688                self.write_keyword("GET_PATH");
28689                self.write("(");
28690                self.generate_expression(&e.this)?;
28691                self.write(", ");
28692                self.generate_expression(&e.expression)?;
28693                self.write(")");
28694            }
28695        } else {
28696            self.write_keyword("JSON_EXTRACT");
28697            self.write("(");
28698            self.generate_expression(&e.this)?;
28699            self.write(", ");
28700            self.generate_expression(&e.expression)?;
28701            for expr in &e.expressions {
28702                self.write(", ");
28703                self.generate_expression(expr)?;
28704            }
28705            self.write(")");
28706        }
28707        Ok(())
28708    }
28709
28710    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28711        // Output: {expr} FORMAT JSON
28712        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28713        if let Some(this) = &e.this {
28714            self.generate_expression(this)?;
28715            self.write_space();
28716        }
28717        self.write_keyword("FORMAT JSON");
28718        Ok(())
28719    }
28720
28721    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28722        // key: value (for JSON objects)
28723        self.generate_expression(&e.this)?;
28724        self.write(": ");
28725        self.generate_expression(&e.expression)?;
28726        Ok(())
28727    }
28728
28729    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28730        // JSON_KEYS(this, expression, expressions...)
28731        self.write_keyword("JSON_KEYS");
28732        self.write("(");
28733        self.generate_expression(&e.this)?;
28734        if let Some(expr) = &e.expression {
28735            self.write(", ");
28736            self.generate_expression(expr)?;
28737        }
28738        for expr in &e.expressions {
28739            self.write(", ");
28740            self.generate_expression(expr)?;
28741        }
28742        self.write(")");
28743        Ok(())
28744    }
28745
28746    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28747        // JSON_KEYS(this, expression)
28748        self.write_keyword("JSON_KEYS");
28749        self.write("(");
28750        self.generate_expression(&e.this)?;
28751        if let Some(expr) = &e.expression {
28752            self.write(", ");
28753            self.generate_expression(expr)?;
28754        }
28755        self.write(")");
28756        Ok(())
28757    }
28758
28759    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28760        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28761        // The path components are concatenated without spaces
28762        let mut path_str = String::new();
28763        for expr in &e.expressions {
28764            match expr {
28765                Expression::JSONPathRoot(_) => {
28766                    path_str.push('$');
28767                }
28768                Expression::JSONPathKey(k) => {
28769                    // .key or ."key" (quote if key has special characters)
28770                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28771                        k.this.as_ref()
28772                    {
28773                        path_str.push('.');
28774                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28775                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28776                        if needs_quoting {
28777                            path_str.push('"');
28778                            path_str.push_str(s);
28779                            path_str.push('"');
28780                        } else {
28781                            path_str.push_str(s);
28782                        }
28783                    }
28784                }
28785                Expression::JSONPathSubscript(s) => {
28786                    // [index]
28787                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28788                        s.this.as_ref()
28789                    {
28790                        path_str.push('[');
28791                        path_str.push_str(n);
28792                        path_str.push(']');
28793                    }
28794                }
28795                _ => {
28796                    // For other path parts, try to generate them
28797                    let mut temp_gen = Self::with_config(self.config.clone());
28798                    temp_gen.generate_expression(expr)?;
28799                    path_str.push_str(&temp_gen.output);
28800                }
28801            }
28802        }
28803        // Output as quoted string
28804        self.write("'");
28805        self.write(&path_str);
28806        self.write("'");
28807        Ok(())
28808    }
28809
28810    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28811        // JSON path filter: ?(predicate)
28812        self.write("?(");
28813        self.generate_expression(&e.this)?;
28814        self.write(")");
28815        Ok(())
28816    }
28817
28818    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28819        // JSON path key: .key or ["key"]
28820        self.write(".");
28821        self.generate_expression(&e.this)?;
28822        Ok(())
28823    }
28824
28825    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28826        // JSON path recursive descent: ..
28827        self.write("..");
28828        if let Some(this) = &e.this {
28829            self.generate_expression(this)?;
28830        }
28831        Ok(())
28832    }
28833
28834    fn generate_json_path_root(&mut self) -> Result<()> {
28835        // JSON path root: $
28836        self.write("$");
28837        Ok(())
28838    }
28839
28840    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28841        // JSON path script: (expression)
28842        self.write("(");
28843        self.generate_expression(&e.this)?;
28844        self.write(")");
28845        Ok(())
28846    }
28847
28848    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28849        // JSON path selector: *
28850        self.generate_expression(&e.this)?;
28851        Ok(())
28852    }
28853
28854    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28855        // JSON path slice: [start:end:step]
28856        self.write("[");
28857        if let Some(start) = &e.start {
28858            self.generate_expression(start)?;
28859        }
28860        self.write(":");
28861        if let Some(end) = &e.end {
28862            self.generate_expression(end)?;
28863        }
28864        if let Some(step) = &e.step {
28865            self.write(":");
28866            self.generate_expression(step)?;
28867        }
28868        self.write("]");
28869        Ok(())
28870    }
28871
28872    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28873        // JSON path subscript: [index] or [*]
28874        self.write("[");
28875        self.generate_expression(&e.this)?;
28876        self.write("]");
28877        Ok(())
28878    }
28879
28880    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28881        // JSON path union: [key1, key2, ...]
28882        self.write("[");
28883        for (i, expr) in e.expressions.iter().enumerate() {
28884            if i > 0 {
28885                self.write(", ");
28886            }
28887            self.generate_expression(expr)?;
28888        }
28889        self.write("]");
28890        Ok(())
28891    }
28892
28893    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28894        // JSON_REMOVE(this, path1, path2, ...)
28895        self.write_keyword("JSON_REMOVE");
28896        self.write("(");
28897        self.generate_expression(&e.this)?;
28898        for expr in &e.expressions {
28899            self.write(", ");
28900            self.generate_expression(expr)?;
28901        }
28902        self.write(")");
28903        Ok(())
28904    }
28905
28906    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28907        // COLUMNS(col1 type, col2 type, ...)
28908        // When pretty printing and content is too wide, format with each column on a separate line
28909        self.write_keyword("COLUMNS");
28910        self.write("(");
28911
28912        if self.config.pretty && !e.expressions.is_empty() {
28913            // First, generate all expressions into strings to check width
28914            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28915            for expr in &e.expressions {
28916                let mut temp_gen = Generator::with_config(self.config.clone());
28917                temp_gen.generate_expression(expr)?;
28918                expr_strings.push(temp_gen.output);
28919            }
28920
28921            // Check if total width exceeds max_text_width
28922            if self.too_wide(&expr_strings) {
28923                // Pretty print: each column on its own line
28924                self.write_newline();
28925                self.indent_level += 1;
28926                for (i, expr_str) in expr_strings.iter().enumerate() {
28927                    if i > 0 {
28928                        self.write(",");
28929                        self.write_newline();
28930                    }
28931                    self.write_indent();
28932                    self.write(expr_str);
28933                }
28934                self.write_newline();
28935                self.indent_level -= 1;
28936                self.write_indent();
28937            } else {
28938                // Compact: all on one line
28939                for (i, expr_str) in expr_strings.iter().enumerate() {
28940                    if i > 0 {
28941                        self.write(", ");
28942                    }
28943                    self.write(expr_str);
28944                }
28945            }
28946        } else {
28947            // Non-pretty mode: compact format
28948            for (i, expr) in e.expressions.iter().enumerate() {
28949                if i > 0 {
28950                    self.write(", ");
28951                }
28952                self.generate_expression(expr)?;
28953            }
28954        }
28955        self.write(")");
28956        Ok(())
28957    }
28958
28959    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28960        // JSON_SET(this, path, value, ...)
28961        self.write_keyword("JSON_SET");
28962        self.write("(");
28963        self.generate_expression(&e.this)?;
28964        for expr in &e.expressions {
28965            self.write(", ");
28966            self.generate_expression(expr)?;
28967        }
28968        self.write(")");
28969        Ok(())
28970    }
28971
28972    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28973        // JSON_STRIP_NULLS(this, expression)
28974        self.write_keyword("JSON_STRIP_NULLS");
28975        self.write("(");
28976        self.generate_expression(&e.this)?;
28977        if let Some(expr) = &e.expression {
28978            self.write(", ");
28979            self.generate_expression(expr)?;
28980        }
28981        self.write(")");
28982        Ok(())
28983    }
28984
28985    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28986        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28987        self.write_keyword("JSON_TABLE");
28988        self.write("(");
28989        self.generate_expression(&e.this)?;
28990        if let Some(path) = &e.path {
28991            self.write(", ");
28992            self.generate_expression(path)?;
28993        }
28994        if let Some(error_handling) = &e.error_handling {
28995            self.write_space();
28996            self.generate_expression(error_handling)?;
28997        }
28998        if let Some(empty_handling) = &e.empty_handling {
28999            self.write_space();
29000            self.generate_expression(empty_handling)?;
29001        }
29002        if let Some(schema) = &e.schema {
29003            self.write_space();
29004            self.generate_expression(schema)?;
29005        }
29006        self.write(")");
29007        Ok(())
29008    }
29009
29010    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
29011        // JSON_TYPE(this)
29012        self.write_keyword("JSON_TYPE");
29013        self.write("(");
29014        self.generate_expression(&e.this)?;
29015        self.write(")");
29016        Ok(())
29017    }
29018
29019    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
29020        // JSON_VALUE(this, path RETURNING type ON condition)
29021        self.write_keyword("JSON_VALUE");
29022        self.write("(");
29023        self.generate_expression(&e.this)?;
29024        if let Some(path) = &e.path {
29025            self.write(", ");
29026            self.generate_expression(path)?;
29027        }
29028        if let Some(returning) = &e.returning {
29029            self.write_space();
29030            self.write_keyword("RETURNING");
29031            self.write_space();
29032            self.generate_expression(returning)?;
29033        }
29034        if let Some(on_condition) = &e.on_condition {
29035            self.write_space();
29036            self.generate_expression(on_condition)?;
29037        }
29038        self.write(")");
29039        Ok(())
29040    }
29041
29042    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
29043        // JSON_VALUE_ARRAY(this)
29044        self.write_keyword("JSON_VALUE_ARRAY");
29045        self.write("(");
29046        self.generate_expression(&e.this)?;
29047        self.write(")");
29048        Ok(())
29049    }
29050
29051    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
29052        // JAROWINKLER_SIMILARITY(str1, str2)
29053        self.write_keyword("JAROWINKLER_SIMILARITY");
29054        self.write("(");
29055        self.generate_expression(&e.this)?;
29056        self.write(", ");
29057        self.generate_expression(&e.expression)?;
29058        self.write(")");
29059        Ok(())
29060    }
29061
29062    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
29063        // Python: this(expressions)
29064        self.generate_expression(&e.this)?;
29065        self.write("(");
29066        for (i, expr) in e.expressions.iter().enumerate() {
29067            if i > 0 {
29068                self.write(", ");
29069            }
29070            self.generate_expression(expr)?;
29071        }
29072        self.write(")");
29073        Ok(())
29074    }
29075
29076    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
29077        // Python: {no}{local}{dual}{before}{after}JOURNAL
29078        if e.no.is_some() {
29079            self.write_keyword("NO ");
29080        }
29081        if let Some(local) = &e.local {
29082            self.generate_expression(local)?;
29083            self.write_space();
29084        }
29085        if e.dual.is_some() {
29086            self.write_keyword("DUAL ");
29087        }
29088        if e.before.is_some() {
29089            self.write_keyword("BEFORE ");
29090        }
29091        if e.after.is_some() {
29092            self.write_keyword("AFTER ");
29093        }
29094        self.write_keyword("JOURNAL");
29095        Ok(())
29096    }
29097
29098    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
29099        // LANGUAGE language_name
29100        self.write_keyword("LANGUAGE");
29101        self.write_space();
29102        self.generate_expression(&e.this)?;
29103        Ok(())
29104    }
29105
29106    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
29107        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
29108        if e.view.is_some() {
29109            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
29110            self.write_keyword("LATERAL VIEW");
29111            if e.outer.is_some() {
29112                self.write_space();
29113                self.write_keyword("OUTER");
29114            }
29115            self.write_space();
29116            self.generate_expression(&e.this)?;
29117            if let Some(alias) = &e.alias {
29118                self.write_space();
29119                self.write(alias);
29120            }
29121        } else {
29122            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
29123            self.write_keyword("LATERAL");
29124            self.write_space();
29125            self.generate_expression(&e.this)?;
29126            if e.ordinality.is_some() {
29127                self.write_space();
29128                self.write_keyword("WITH ORDINALITY");
29129            }
29130            if let Some(alias) = &e.alias {
29131                self.write_space();
29132                self.write_keyword("AS");
29133                self.write_space();
29134                self.write(alias);
29135                if !e.column_aliases.is_empty() {
29136                    self.write("(");
29137                    for (i, col) in e.column_aliases.iter().enumerate() {
29138                        if i > 0 {
29139                            self.write(", ");
29140                        }
29141                        self.write(col);
29142                    }
29143                    self.write(")");
29144                }
29145            }
29146        }
29147        Ok(())
29148    }
29149
29150    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29151        // Python: LIKE this [options]
29152        self.write_keyword("LIKE");
29153        self.write_space();
29154        self.generate_expression(&e.this)?;
29155        for expr in &e.expressions {
29156            self.write_space();
29157            self.generate_expression(expr)?;
29158        }
29159        Ok(())
29160    }
29161
29162    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29163        self.write_keyword("LIMIT");
29164        self.write_space();
29165        self.write_limit_expr(&e.this)?;
29166        if e.percent {
29167            self.write_space();
29168            self.write_keyword("PERCENT");
29169        }
29170        // Emit any comments that were captured from before the LIMIT keyword
29171        for comment in &e.comments {
29172            self.write(" ");
29173            self.write_formatted_comment(comment);
29174        }
29175        Ok(())
29176    }
29177
29178    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29179        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29180        if e.percent.is_some() {
29181            self.write_keyword(" PERCENT");
29182        }
29183        if e.rows.is_some() {
29184            self.write_keyword(" ROWS");
29185        }
29186        if e.with_ties.is_some() {
29187            self.write_keyword(" WITH TIES");
29188        } else if e.rows.is_some() {
29189            self.write_keyword(" ONLY");
29190        }
29191        Ok(())
29192    }
29193
29194    fn generate_list(&mut self, e: &List) -> Result<()> {
29195        use crate::dialects::DialectType;
29196        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29197
29198        // Check if this is a subquery-based list (LIST(SELECT ...))
29199        if e.expressions.len() == 1 {
29200            if let Expression::Select(_) = &e.expressions[0] {
29201                self.write_keyword("LIST");
29202                self.write("(");
29203                self.generate_expression(&e.expressions[0])?;
29204                self.write(")");
29205                return Ok(());
29206            }
29207        }
29208
29209        // For Materialize, output as LIST[expr, expr, ...]
29210        if is_materialize {
29211            self.write_keyword("LIST");
29212            self.write("[");
29213            for (i, expr) in e.expressions.iter().enumerate() {
29214                if i > 0 {
29215                    self.write(", ");
29216                }
29217                self.generate_expression(expr)?;
29218            }
29219            self.write("]");
29220        } else {
29221            // For other dialects, output as LIST(expr, expr, ...)
29222            self.write_keyword("LIST");
29223            self.write("(");
29224            for (i, expr) in e.expressions.iter().enumerate() {
29225                if i > 0 {
29226                    self.write(", ");
29227                }
29228                self.generate_expression(expr)?;
29229            }
29230            self.write(")");
29231        }
29232        Ok(())
29233    }
29234
29235    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29236        // Check if this is a subquery-based map (MAP(SELECT ...))
29237        if let Expression::Select(_) = &*e.this {
29238            self.write_keyword("MAP");
29239            self.write("(");
29240            self.generate_expression(&e.this)?;
29241            self.write(")");
29242            return Ok(());
29243        }
29244
29245        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29246
29247        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29248        self.write_keyword("MAP");
29249        if is_duckdb {
29250            self.write(" {");
29251        } else {
29252            self.write("[");
29253        }
29254        if let Expression::Struct(s) = &*e.this {
29255            for (i, (_, expr)) in s.fields.iter().enumerate() {
29256                if i > 0 {
29257                    self.write(", ");
29258                }
29259                if let Expression::PropertyEQ(op) = expr {
29260                    self.generate_expression(&op.left)?;
29261                    if is_duckdb {
29262                        self.write(": ");
29263                    } else {
29264                        self.write(" => ");
29265                    }
29266                    self.generate_expression(&op.right)?;
29267                } else {
29268                    self.generate_expression(expr)?;
29269                }
29270            }
29271        }
29272        if is_duckdb {
29273            self.write("}");
29274        } else {
29275            self.write("]");
29276        }
29277        Ok(())
29278    }
29279
29280    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29281        // Python: LOCALTIME or LOCALTIME(precision)
29282        self.write_keyword("LOCALTIME");
29283        if let Some(precision) = &e.this {
29284            self.write("(");
29285            self.generate_expression(precision)?;
29286            self.write(")");
29287        }
29288        Ok(())
29289    }
29290
29291    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29292        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29293        self.write_keyword("LOCALTIMESTAMP");
29294        if let Some(precision) = &e.this {
29295            self.write("(");
29296            self.generate_expression(precision)?;
29297            self.write(")");
29298        }
29299        Ok(())
29300    }
29301
29302    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29303        // LOCATION 'path'
29304        self.write_keyword("LOCATION");
29305        self.write_space();
29306        self.generate_expression(&e.this)?;
29307        Ok(())
29308    }
29309
29310    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29311        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29312        if e.update.is_some() {
29313            if e.key.is_some() {
29314                self.write_keyword("FOR NO KEY UPDATE");
29315            } else {
29316                self.write_keyword("FOR UPDATE");
29317            }
29318        } else {
29319            if e.key.is_some() {
29320                self.write_keyword("FOR KEY SHARE");
29321            } else {
29322                self.write_keyword("FOR SHARE");
29323            }
29324        }
29325        if !e.expressions.is_empty() {
29326            self.write_keyword(" OF ");
29327            for (i, expr) in e.expressions.iter().enumerate() {
29328                if i > 0 {
29329                    self.write(", ");
29330                }
29331                self.generate_expression(expr)?;
29332            }
29333        }
29334        // Handle wait option following Python sqlglot convention:
29335        // - Boolean(true) -> NOWAIT
29336        // - Boolean(false) -> SKIP LOCKED
29337        // - Literal (number) -> WAIT n
29338        if let Some(wait) = &e.wait {
29339            match wait.as_ref() {
29340                Expression::Boolean(b) => {
29341                    if b.value {
29342                        self.write_keyword(" NOWAIT");
29343                    } else {
29344                        self.write_keyword(" SKIP LOCKED");
29345                    }
29346                }
29347                _ => {
29348                    // It's a literal (number), output WAIT n
29349                    self.write_keyword(" WAIT ");
29350                    self.generate_expression(wait)?;
29351                }
29352            }
29353        }
29354        Ok(())
29355    }
29356
29357    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29358        // LOCK property
29359        self.write_keyword("LOCK");
29360        self.write_space();
29361        self.generate_expression(&e.this)?;
29362        Ok(())
29363    }
29364
29365    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29366        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29367        self.write_keyword("LOCKING");
29368        self.write_space();
29369        self.write(&e.kind);
29370        if let Some(this) = &e.this {
29371            self.write_space();
29372            self.generate_expression(this)?;
29373        }
29374        if let Some(for_or_in) = &e.for_or_in {
29375            self.write_space();
29376            self.generate_expression(for_or_in)?;
29377        }
29378        if let Some(lock_type) = &e.lock_type {
29379            self.write_space();
29380            self.generate_expression(lock_type)?;
29381        }
29382        if e.override_.is_some() {
29383            self.write_keyword(" OVERRIDE");
29384        }
29385        Ok(())
29386    }
29387
29388    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29389        // this expression
29390        self.generate_expression(&e.this)?;
29391        self.write_space();
29392        self.generate_expression(&e.expression)?;
29393        Ok(())
29394    }
29395
29396    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29397        // [NO] LOG
29398        if e.no.is_some() {
29399            self.write_keyword("NO ");
29400        }
29401        self.write_keyword("LOG");
29402        Ok(())
29403    }
29404
29405    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29406        // MD5(this, expressions...)
29407        self.write_keyword("MD5");
29408        self.write("(");
29409        self.generate_expression(&e.this)?;
29410        for expr in &e.expressions {
29411            self.write(", ");
29412            self.generate_expression(expr)?;
29413        }
29414        self.write(")");
29415        Ok(())
29416    }
29417
29418    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29419        // ML.FORECAST(model, [params])
29420        self.write_keyword("ML.FORECAST");
29421        self.write("(");
29422        self.generate_expression(&e.this)?;
29423        if let Some(expression) = &e.expression {
29424            self.write(", ");
29425            self.generate_expression(expression)?;
29426        }
29427        if let Some(params) = &e.params_struct {
29428            self.write(", ");
29429            self.generate_expression(params)?;
29430        }
29431        self.write(")");
29432        Ok(())
29433    }
29434
29435    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29436        // ML.TRANSLATE(model, input, [params])
29437        self.write_keyword("ML.TRANSLATE");
29438        self.write("(");
29439        self.generate_expression(&e.this)?;
29440        self.write(", ");
29441        self.generate_expression(&e.expression)?;
29442        if let Some(params) = &e.params_struct {
29443            self.write(", ");
29444            self.generate_expression(params)?;
29445        }
29446        self.write(")");
29447        Ok(())
29448    }
29449
29450    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29451        // MAKE_INTERVAL(years => x, months => y, ...)
29452        self.write_keyword("MAKE_INTERVAL");
29453        self.write("(");
29454        let mut first = true;
29455        if let Some(year) = &e.year {
29456            self.write("years => ");
29457            self.generate_expression(year)?;
29458            first = false;
29459        }
29460        if let Some(month) = &e.month {
29461            if !first {
29462                self.write(", ");
29463            }
29464            self.write("months => ");
29465            self.generate_expression(month)?;
29466            first = false;
29467        }
29468        if let Some(week) = &e.week {
29469            if !first {
29470                self.write(", ");
29471            }
29472            self.write("weeks => ");
29473            self.generate_expression(week)?;
29474            first = false;
29475        }
29476        if let Some(day) = &e.day {
29477            if !first {
29478                self.write(", ");
29479            }
29480            self.write("days => ");
29481            self.generate_expression(day)?;
29482            first = false;
29483        }
29484        if let Some(hour) = &e.hour {
29485            if !first {
29486                self.write(", ");
29487            }
29488            self.write("hours => ");
29489            self.generate_expression(hour)?;
29490            first = false;
29491        }
29492        if let Some(minute) = &e.minute {
29493            if !first {
29494                self.write(", ");
29495            }
29496            self.write("mins => ");
29497            self.generate_expression(minute)?;
29498            first = false;
29499        }
29500        if let Some(second) = &e.second {
29501            if !first {
29502                self.write(", ");
29503            }
29504            self.write("secs => ");
29505            self.generate_expression(second)?;
29506        }
29507        self.write(")");
29508        Ok(())
29509    }
29510
29511    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29512        // MANHATTAN_DISTANCE(vector1, vector2)
29513        self.write_keyword("MANHATTAN_DISTANCE");
29514        self.write("(");
29515        self.generate_expression(&e.this)?;
29516        self.write(", ");
29517        self.generate_expression(&e.expression)?;
29518        self.write(")");
29519        Ok(())
29520    }
29521
29522    fn generate_map(&mut self, e: &Map) -> Result<()> {
29523        // MAP(key1, value1, key2, value2, ...)
29524        self.write_keyword("MAP");
29525        self.write("(");
29526        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29527            if i > 0 {
29528                self.write(", ");
29529            }
29530            self.generate_expression(key)?;
29531            self.write(", ");
29532            self.generate_expression(value)?;
29533        }
29534        self.write(")");
29535        Ok(())
29536    }
29537
29538    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29539        // MAP_CAT(map1, map2)
29540        self.write_keyword("MAP_CAT");
29541        self.write("(");
29542        self.generate_expression(&e.this)?;
29543        self.write(", ");
29544        self.generate_expression(&e.expression)?;
29545        self.write(")");
29546        Ok(())
29547    }
29548
29549    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29550        // MAP_DELETE(map, key1, key2, ...)
29551        self.write_keyword("MAP_DELETE");
29552        self.write("(");
29553        self.generate_expression(&e.this)?;
29554        for expr in &e.expressions {
29555            self.write(", ");
29556            self.generate_expression(expr)?;
29557        }
29558        self.write(")");
29559        Ok(())
29560    }
29561
29562    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29563        // MAP_INSERT(map, key, value, [update_flag])
29564        self.write_keyword("MAP_INSERT");
29565        self.write("(");
29566        self.generate_expression(&e.this)?;
29567        if let Some(key) = &e.key {
29568            self.write(", ");
29569            self.generate_expression(key)?;
29570        }
29571        if let Some(value) = &e.value {
29572            self.write(", ");
29573            self.generate_expression(value)?;
29574        }
29575        if let Some(update_flag) = &e.update_flag {
29576            self.write(", ");
29577            self.generate_expression(update_flag)?;
29578        }
29579        self.write(")");
29580        Ok(())
29581    }
29582
29583    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29584        // MAP_PICK(map, key1, key2, ...)
29585        self.write_keyword("MAP_PICK");
29586        self.write("(");
29587        self.generate_expression(&e.this)?;
29588        for expr in &e.expressions {
29589            self.write(", ");
29590            self.generate_expression(expr)?;
29591        }
29592        self.write(")");
29593        Ok(())
29594    }
29595
29596    fn generate_masking_policy_column_constraint(
29597        &mut self,
29598        e: &MaskingPolicyColumnConstraint,
29599    ) -> Result<()> {
29600        // Python: MASKING POLICY name [USING (cols)]
29601        self.write_keyword("MASKING POLICY");
29602        self.write_space();
29603        self.generate_expression(&e.this)?;
29604        if !e.expressions.is_empty() {
29605            self.write_keyword(" USING");
29606            self.write(" (");
29607            for (i, expr) in e.expressions.iter().enumerate() {
29608                if i > 0 {
29609                    self.write(", ");
29610                }
29611                self.generate_expression(expr)?;
29612            }
29613            self.write(")");
29614        }
29615        Ok(())
29616    }
29617
29618    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29619        if matches!(
29620            self.config.dialect,
29621            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29622        ) {
29623            if e.expressions.len() > 1 {
29624                self.write("(");
29625            }
29626            for (i, expr) in e.expressions.iter().enumerate() {
29627                if i > 0 {
29628                    self.write_keyword(" OR ");
29629                }
29630                self.generate_expression(expr)?;
29631                self.write_space();
29632                self.write("@@");
29633                self.write_space();
29634                self.generate_expression(&e.this)?;
29635            }
29636            if e.expressions.len() > 1 {
29637                self.write(")");
29638            }
29639            return Ok(());
29640        }
29641
29642        // MATCH(columns) AGAINST(expr [modifier])
29643        self.write_keyword("MATCH");
29644        self.write("(");
29645        for (i, expr) in e.expressions.iter().enumerate() {
29646            if i > 0 {
29647                self.write(", ");
29648            }
29649            self.generate_expression(expr)?;
29650        }
29651        self.write(")");
29652        self.write_keyword(" AGAINST");
29653        self.write("(");
29654        self.generate_expression(&e.this)?;
29655        if let Some(modifier) = &e.modifier {
29656            self.write_space();
29657            self.generate_expression(modifier)?;
29658        }
29659        self.write(")");
29660        Ok(())
29661    }
29662
29663    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29664        // Python: [window_frame] this
29665        if let Some(window_frame) = &e.window_frame {
29666            self.write(&format!("{:?}", window_frame).to_uppercase());
29667            self.write_space();
29668        }
29669        self.generate_expression(&e.this)?;
29670        Ok(())
29671    }
29672
29673    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29674        // MATERIALIZED [this]
29675        self.write_keyword("MATERIALIZED");
29676        if let Some(this) = &e.this {
29677            self.write_space();
29678            self.generate_expression(this)?;
29679        }
29680        Ok(())
29681    }
29682
29683    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29684        // MERGE INTO target USING source ON condition WHEN ...
29685        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29686        if let Some(with_) = &e.with_ {
29687            self.generate_expression(with_)?;
29688            self.write_space();
29689        }
29690        self.write_keyword("MERGE INTO");
29691        self.write_space();
29692        self.generate_expression(&e.this)?;
29693
29694        // USING clause - newline before in pretty mode
29695        if self.config.pretty {
29696            self.write_newline();
29697            self.write_indent();
29698        } else {
29699            self.write_space();
29700        }
29701        self.write_keyword("USING");
29702        self.write_space();
29703        self.generate_expression(&e.using)?;
29704
29705        // ON clause - newline before in pretty mode
29706        if let Some(on) = &e.on {
29707            if self.config.pretty {
29708                self.write_newline();
29709                self.write_indent();
29710            } else {
29711                self.write_space();
29712            }
29713            self.write_keyword("ON");
29714            self.write_space();
29715            self.generate_expression(on)?;
29716        }
29717        // DuckDB USING (key_columns) clause
29718        if let Some(using_cond) = &e.using_cond {
29719            self.write_space();
29720            self.write_keyword("USING");
29721            self.write_space();
29722            self.write("(");
29723            // using_cond is a Tuple containing the column identifiers
29724            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29725                for (i, col) in tuple.expressions.iter().enumerate() {
29726                    if i > 0 {
29727                        self.write(", ");
29728                    }
29729                    self.generate_expression(col)?;
29730                }
29731            } else {
29732                self.generate_expression(using_cond)?;
29733            }
29734            self.write(")");
29735        }
29736        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29737        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29738        if matches!(
29739            self.config.dialect,
29740            Some(crate::DialectType::PostgreSQL)
29741                | Some(crate::DialectType::Redshift)
29742                | Some(crate::DialectType::Trino)
29743                | Some(crate::DialectType::Presto)
29744                | Some(crate::DialectType::Athena)
29745        ) {
29746            let mut names = Vec::new();
29747            match e.this.as_ref() {
29748                Expression::Alias(a) => {
29749                    // e.g., "x AS z" -> strip both "x" and "z"
29750                    if let Expression::Table(t) = &a.this {
29751                        names.push(t.name.name.clone());
29752                    } else if let Expression::Identifier(id) = &a.this {
29753                        names.push(id.name.clone());
29754                    }
29755                    names.push(a.alias.name.clone());
29756                }
29757                Expression::Table(t) => {
29758                    names.push(t.name.name.clone());
29759                }
29760                Expression::Identifier(id) => {
29761                    names.push(id.name.clone());
29762                }
29763                _ => {}
29764            }
29765            self.merge_strip_qualifiers = names;
29766        }
29767
29768        // WHEN clauses - newline before each in pretty mode
29769        if let Some(whens) = &e.whens {
29770            if self.config.pretty {
29771                self.write_newline();
29772                self.write_indent();
29773            } else {
29774                self.write_space();
29775            }
29776            self.generate_expression(whens)?;
29777        }
29778
29779        // Restore merge_strip_qualifiers
29780        self.merge_strip_qualifiers = saved_merge_strip;
29781
29782        // OUTPUT/RETURNING clause - newline before in pretty mode
29783        if let Some(returning) = &e.returning {
29784            if self.config.pretty {
29785                self.write_newline();
29786                self.write_indent();
29787            } else {
29788                self.write_space();
29789            }
29790            self.generate_expression(returning)?;
29791        }
29792        Ok(())
29793    }
29794
29795    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29796        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29797        if e.no.is_some() {
29798            self.write_keyword("NO MERGEBLOCKRATIO");
29799        } else if e.default.is_some() {
29800            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29801        } else {
29802            self.write_keyword("MERGEBLOCKRATIO");
29803            self.write("=");
29804            if let Some(this) = &e.this {
29805                self.generate_expression(this)?;
29806            }
29807            if e.percent.is_some() {
29808                self.write_keyword(" PERCENT");
29809            }
29810        }
29811        Ok(())
29812    }
29813
29814    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29815        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29816        self.write_keyword("TTL");
29817        let pretty_clickhouse = self.config.pretty
29818            && matches!(
29819                self.config.dialect,
29820                Some(crate::dialects::DialectType::ClickHouse)
29821            );
29822
29823        if pretty_clickhouse {
29824            self.write_newline();
29825            self.indent_level += 1;
29826            for (i, expr) in e.expressions.iter().enumerate() {
29827                if i > 0 {
29828                    self.write(",");
29829                    self.write_newline();
29830                }
29831                self.write_indent();
29832                self.generate_expression(expr)?;
29833            }
29834            self.indent_level -= 1;
29835        } else {
29836            self.write_space();
29837            for (i, expr) in e.expressions.iter().enumerate() {
29838                if i > 0 {
29839                    self.write(", ");
29840                }
29841                self.generate_expression(expr)?;
29842            }
29843        }
29844
29845        if let Some(where_) = &e.where_ {
29846            if pretty_clickhouse {
29847                self.write_newline();
29848                if let Expression::Where(w) = where_.as_ref() {
29849                    self.write_indent();
29850                    self.write_keyword("WHERE");
29851                    self.write_newline();
29852                    self.indent_level += 1;
29853                    self.write_indent();
29854                    self.generate_expression(&w.this)?;
29855                    self.indent_level -= 1;
29856                } else {
29857                    self.write_indent();
29858                    self.generate_expression(where_)?;
29859                }
29860            } else {
29861                self.write_space();
29862                self.generate_expression(where_)?;
29863            }
29864        }
29865        if let Some(group) = &e.group {
29866            if pretty_clickhouse {
29867                self.write_newline();
29868                if let Expression::Group(g) = group.as_ref() {
29869                    self.write_indent();
29870                    self.write_keyword("GROUP BY");
29871                    self.write_newline();
29872                    self.indent_level += 1;
29873                    for (i, expr) in g.expressions.iter().enumerate() {
29874                        if i > 0 {
29875                            self.write(",");
29876                            self.write_newline();
29877                        }
29878                        self.write_indent();
29879                        self.generate_expression(expr)?;
29880                    }
29881                    self.indent_level -= 1;
29882                } else {
29883                    self.write_indent();
29884                    self.generate_expression(group)?;
29885                }
29886            } else {
29887                self.write_space();
29888                self.generate_expression(group)?;
29889            }
29890        }
29891        if let Some(aggregates) = &e.aggregates {
29892            if pretty_clickhouse {
29893                self.write_newline();
29894                self.write_indent();
29895                self.write_keyword("SET");
29896                self.write_newline();
29897                self.indent_level += 1;
29898                if let Expression::Tuple(t) = aggregates.as_ref() {
29899                    for (i, agg) in t.expressions.iter().enumerate() {
29900                        if i > 0 {
29901                            self.write(",");
29902                            self.write_newline();
29903                        }
29904                        self.write_indent();
29905                        self.generate_expression(agg)?;
29906                    }
29907                } else {
29908                    self.write_indent();
29909                    self.generate_expression(aggregates)?;
29910                }
29911                self.indent_level -= 1;
29912            } else {
29913                self.write_space();
29914                self.write_keyword("SET");
29915                self.write_space();
29916                self.generate_expression(aggregates)?;
29917            }
29918        }
29919        Ok(())
29920    }
29921
29922    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29923        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29924        self.generate_expression(&e.this)?;
29925        if e.delete.is_some() {
29926            self.write_keyword(" DELETE");
29927        }
29928        if let Some(recompress) = &e.recompress {
29929            self.write_keyword(" RECOMPRESS ");
29930            self.generate_expression(recompress)?;
29931        }
29932        if let Some(to_disk) = &e.to_disk {
29933            self.write_keyword(" TO DISK ");
29934            self.generate_expression(to_disk)?;
29935        }
29936        if let Some(to_volume) = &e.to_volume {
29937            self.write_keyword(" TO VOLUME ");
29938            self.generate_expression(to_volume)?;
29939        }
29940        Ok(())
29941    }
29942
29943    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29944        // MINHASH(this, expressions...)
29945        self.write_keyword("MINHASH");
29946        self.write("(");
29947        self.generate_expression(&e.this)?;
29948        for expr in &e.expressions {
29949            self.write(", ");
29950            self.generate_expression(expr)?;
29951        }
29952        self.write(")");
29953        Ok(())
29954    }
29955
29956    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29957        // model!attribute - Snowflake syntax
29958        self.generate_expression(&e.this)?;
29959        self.write("!");
29960        self.generate_expression(&e.expression)?;
29961        Ok(())
29962    }
29963
29964    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29965        // MONTHNAME(this)
29966        self.write_keyword("MONTHNAME");
29967        self.write("(");
29968        self.generate_expression(&e.this)?;
29969        self.write(")");
29970        Ok(())
29971    }
29972
29973    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29974        // Output leading comments
29975        for comment in &e.leading_comments {
29976            self.write_formatted_comment(comment);
29977            if self.config.pretty {
29978                self.write_newline();
29979                self.write_indent();
29980            } else {
29981                self.write_space();
29982            }
29983        }
29984        // Python: INSERT kind expressions source
29985        self.write_keyword("INSERT");
29986        self.write_space();
29987        self.write(&e.kind);
29988        if self.config.pretty {
29989            self.indent_level += 1;
29990            for expr in &e.expressions {
29991                self.write_newline();
29992                self.write_indent();
29993                self.generate_expression(expr)?;
29994            }
29995            self.indent_level -= 1;
29996        } else {
29997            for expr in &e.expressions {
29998                self.write_space();
29999                self.generate_expression(expr)?;
30000            }
30001        }
30002        if let Some(source) = &e.source {
30003            if self.config.pretty {
30004                self.write_newline();
30005                self.write_indent();
30006            } else {
30007                self.write_space();
30008            }
30009            self.generate_expression(source)?;
30010        }
30011        Ok(())
30012    }
30013
30014    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
30015        // Python: NEXT VALUE FOR this [OVER (order)]
30016        self.write_keyword("NEXT VALUE FOR");
30017        self.write_space();
30018        self.generate_expression(&e.this)?;
30019        if let Some(order) = &e.order {
30020            self.write_space();
30021            self.write_keyword("OVER");
30022            self.write(" (");
30023            self.generate_expression(order)?;
30024            self.write(")");
30025        }
30026        Ok(())
30027    }
30028
30029    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
30030        // NORMAL(mean, stddev, gen)
30031        self.write_keyword("NORMAL");
30032        self.write("(");
30033        self.generate_expression(&e.this)?;
30034        if let Some(stddev) = &e.stddev {
30035            self.write(", ");
30036            self.generate_expression(stddev)?;
30037        }
30038        if let Some(gen) = &e.gen {
30039            self.write(", ");
30040            self.generate_expression(gen)?;
30041        }
30042        self.write(")");
30043        Ok(())
30044    }
30045
30046    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
30047        // NORMALIZE(this, form) or CASEFOLD version
30048        if e.is_casefold.is_some() {
30049            self.write_keyword("NORMALIZE_AND_CASEFOLD");
30050        } else {
30051            self.write_keyword("NORMALIZE");
30052        }
30053        self.write("(");
30054        self.generate_expression(&e.this)?;
30055        if let Some(form) = &e.form {
30056            self.write(", ");
30057            self.generate_expression(form)?;
30058        }
30059        self.write(")");
30060        Ok(())
30061    }
30062
30063    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
30064        // Python: [NOT ]NULL
30065        if e.allow_null.is_none() {
30066            self.write_keyword("NOT ");
30067        }
30068        self.write_keyword("NULL");
30069        Ok(())
30070    }
30071
30072    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
30073        // NULLIF(this, expression)
30074        self.write_keyword("NULLIF");
30075        self.write("(");
30076        self.generate_expression(&e.this)?;
30077        self.write(", ");
30078        self.generate_expression(&e.expression)?;
30079        self.write(")");
30080        Ok(())
30081    }
30082
30083    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
30084        // FORMAT(this, format, culture)
30085        self.write_keyword("FORMAT");
30086        self.write("(");
30087        self.generate_expression(&e.this)?;
30088        self.write(", '");
30089        self.write(&e.format);
30090        self.write("'");
30091        if let Some(culture) = &e.culture {
30092            self.write(", ");
30093            self.generate_expression(culture)?;
30094        }
30095        self.write(")");
30096        Ok(())
30097    }
30098
30099    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
30100        // OBJECT_AGG(key, value)
30101        self.write_keyword("OBJECT_AGG");
30102        self.write("(");
30103        self.generate_expression(&e.this)?;
30104        self.write(", ");
30105        self.generate_expression(&e.expression)?;
30106        self.write(")");
30107        Ok(())
30108    }
30109
30110    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
30111        // Python: Just returns the name
30112        self.generate_expression(&e.this)?;
30113        Ok(())
30114    }
30115
30116    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
30117        // OBJECT_INSERT(obj, key, value, [update_flag])
30118        self.write_keyword("OBJECT_INSERT");
30119        self.write("(");
30120        self.generate_expression(&e.this)?;
30121        if let Some(key) = &e.key {
30122            self.write(", ");
30123            self.generate_expression(key)?;
30124        }
30125        if let Some(value) = &e.value {
30126            self.write(", ");
30127            self.generate_expression(value)?;
30128        }
30129        if let Some(update_flag) = &e.update_flag {
30130            self.write(", ");
30131            self.generate_expression(update_flag)?;
30132        }
30133        self.write(")");
30134        Ok(())
30135    }
30136
30137    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
30138        // OFFSET value [ROW|ROWS]
30139        self.write_keyword("OFFSET");
30140        self.write_space();
30141        self.generate_expression(&e.this)?;
30142        // Output ROWS keyword only for TSQL/Oracle targets
30143        if e.rows == Some(true)
30144            && matches!(
30145                self.config.dialect,
30146                Some(crate::dialects::DialectType::TSQL)
30147                    | Some(crate::dialects::DialectType::Oracle)
30148            )
30149        {
30150            self.write_space();
30151            self.write_keyword("ROWS");
30152        }
30153        Ok(())
30154    }
30155
30156    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30157        // QUALIFY condition (Snowflake/BigQuery)
30158        self.write_keyword("QUALIFY");
30159        self.write_space();
30160        self.generate_expression(&e.this)?;
30161        Ok(())
30162    }
30163
30164    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30165        // ON CLUSTER cluster_name
30166        self.write_keyword("ON CLUSTER");
30167        self.write_space();
30168        self.generate_expression(&e.this)?;
30169        Ok(())
30170    }
30171
30172    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30173        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30174        self.write_keyword("ON COMMIT");
30175        if e.delete.is_some() {
30176            self.write_keyword(" DELETE ROWS");
30177        } else {
30178            self.write_keyword(" PRESERVE ROWS");
30179        }
30180        Ok(())
30181    }
30182
30183    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30184        // Python: error/empty/null handling
30185        if let Some(empty) = &e.empty {
30186            self.generate_expression(empty)?;
30187            self.write_keyword(" ON EMPTY");
30188        }
30189        if let Some(error) = &e.error {
30190            if e.empty.is_some() {
30191                self.write_space();
30192            }
30193            self.generate_expression(error)?;
30194            self.write_keyword(" ON ERROR");
30195        }
30196        if let Some(null) = &e.null {
30197            if e.empty.is_some() || e.error.is_some() {
30198                self.write_space();
30199            }
30200            self.generate_expression(null)?;
30201            self.write_keyword(" ON NULL");
30202        }
30203        Ok(())
30204    }
30205
30206    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30207        // Materialize doesn't support ON CONFLICT - skip entirely
30208        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30209            return Ok(());
30210        }
30211        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30212        if e.duplicate.is_some() {
30213            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30214            self.write_keyword("ON DUPLICATE KEY UPDATE");
30215            for (i, expr) in e.expressions.iter().enumerate() {
30216                if i > 0 {
30217                    self.write(",");
30218                }
30219                self.write_space();
30220                self.generate_expression(expr)?;
30221            }
30222            return Ok(());
30223        } else {
30224            self.write_keyword("ON CONFLICT");
30225        }
30226        if let Some(constraint) = &e.constraint {
30227            self.write_keyword(" ON CONSTRAINT ");
30228            self.generate_expression(constraint)?;
30229        }
30230        if let Some(conflict_keys) = &e.conflict_keys {
30231            // conflict_keys can be a Tuple containing expressions
30232            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30233                self.write("(");
30234                for (i, expr) in t.expressions.iter().enumerate() {
30235                    if i > 0 {
30236                        self.write(", ");
30237                    }
30238                    self.generate_expression(expr)?;
30239                }
30240                self.write(")");
30241            } else {
30242                self.write("(");
30243                self.generate_expression(conflict_keys)?;
30244                self.write(")");
30245            }
30246        }
30247        if let Some(index_predicate) = &e.index_predicate {
30248            self.write_keyword(" WHERE ");
30249            self.generate_expression(index_predicate)?;
30250        }
30251        if let Some(action) = &e.action {
30252            // Check if action is "NOTHING" or an UPDATE set
30253            if let Expression::Identifier(id) = action.as_ref() {
30254                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30255                    self.write_keyword(" DO NOTHING");
30256                } else {
30257                    self.write_keyword(" DO ");
30258                    self.generate_expression(action)?;
30259                }
30260            } else if let Expression::Tuple(t) = action.as_ref() {
30261                // DO UPDATE SET col1 = val1, col2 = val2
30262                self.write_keyword(" DO UPDATE SET ");
30263                for (i, expr) in t.expressions.iter().enumerate() {
30264                    if i > 0 {
30265                        self.write(", ");
30266                    }
30267                    self.generate_expression(expr)?;
30268                }
30269            } else {
30270                self.write_keyword(" DO ");
30271                self.generate_expression(action)?;
30272            }
30273        }
30274        // WHERE clause for the UPDATE action
30275        if let Some(where_) = &e.where_ {
30276            self.write_keyword(" WHERE ");
30277            self.generate_expression(where_)?;
30278        }
30279        Ok(())
30280    }
30281
30282    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30283        // ON property_value
30284        self.write_keyword("ON");
30285        self.write_space();
30286        self.generate_expression(&e.this)?;
30287        Ok(())
30288    }
30289
30290    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30291        // Python: this expression (e.g., column opclass)
30292        self.generate_expression(&e.this)?;
30293        self.write_space();
30294        self.generate_expression(&e.expression)?;
30295        Ok(())
30296    }
30297
30298    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30299        // Python: OPENJSON(this[, path]) [WITH (columns)]
30300        self.write_keyword("OPENJSON");
30301        self.write("(");
30302        self.generate_expression(&e.this)?;
30303        if let Some(path) = &e.path {
30304            self.write(", ");
30305            self.generate_expression(path)?;
30306        }
30307        self.write(")");
30308        if !e.expressions.is_empty() {
30309            self.write_keyword(" WITH");
30310            if self.config.pretty {
30311                self.write(" (\n");
30312                self.indent_level += 2;
30313                for (i, expr) in e.expressions.iter().enumerate() {
30314                    if i > 0 {
30315                        self.write(",\n");
30316                    }
30317                    self.write_indent();
30318                    self.generate_expression(expr)?;
30319                }
30320                self.write("\n");
30321                self.indent_level -= 2;
30322                self.write(")");
30323            } else {
30324                self.write(" (");
30325                for (i, expr) in e.expressions.iter().enumerate() {
30326                    if i > 0 {
30327                        self.write(", ");
30328                    }
30329                    self.generate_expression(expr)?;
30330                }
30331                self.write(")");
30332            }
30333        }
30334        Ok(())
30335    }
30336
30337    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30338        // Python: this kind [path] [AS JSON]
30339        self.generate_expression(&e.this)?;
30340        self.write_space();
30341        // Use parsed data_type if available, otherwise fall back to kind string
30342        if let Some(ref dt) = e.data_type {
30343            self.generate_data_type(dt)?;
30344        } else if !e.kind.is_empty() {
30345            self.write(&e.kind);
30346        }
30347        if let Some(path) = &e.path {
30348            self.write_space();
30349            self.generate_expression(path)?;
30350        }
30351        if e.as_json.is_some() {
30352            self.write_keyword(" AS JSON");
30353        }
30354        Ok(())
30355    }
30356
30357    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30358        // this OPERATOR(op) expression
30359        self.generate_expression(&e.this)?;
30360        self.write_space();
30361        if let Some(op) = &e.operator {
30362            self.write_keyword("OPERATOR");
30363            self.write("(");
30364            self.generate_expression(op)?;
30365            self.write(")");
30366        }
30367        // Emit inline comments between OPERATOR() and the RHS
30368        for comment in &e.comments {
30369            self.write_space();
30370            self.write_formatted_comment(comment);
30371        }
30372        self.write_space();
30373        self.generate_expression(&e.expression)?;
30374        Ok(())
30375    }
30376
30377    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30378        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30379        self.write_keyword("ORDER BY");
30380        let pretty_clickhouse_single_paren = self.config.pretty
30381            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30382            && e.expressions.len() == 1
30383            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30384        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30385            && e.expressions.len() == 1
30386            && matches!(e.expressions[0].this, Expression::Tuple(_))
30387            && !e.expressions[0].desc
30388            && e.expressions[0].nulls_first.is_none();
30389
30390        if pretty_clickhouse_single_paren {
30391            self.write_space();
30392            if let Expression::Paren(p) = &e.expressions[0].this {
30393                self.write("(");
30394                self.write_newline();
30395                self.indent_level += 1;
30396                self.write_indent();
30397                self.generate_expression(&p.this)?;
30398                self.indent_level -= 1;
30399                self.write_newline();
30400                self.write(")");
30401            }
30402            return Ok(());
30403        }
30404
30405        if clickhouse_single_tuple {
30406            self.write_space();
30407            if let Expression::Tuple(t) = &e.expressions[0].this {
30408                self.write("(");
30409                for (i, expr) in t.expressions.iter().enumerate() {
30410                    if i > 0 {
30411                        self.write(", ");
30412                    }
30413                    self.generate_expression(expr)?;
30414                }
30415                self.write(")");
30416            }
30417            return Ok(());
30418        }
30419
30420        self.write_space();
30421        for (i, ordered) in e.expressions.iter().enumerate() {
30422            if i > 0 {
30423                self.write(", ");
30424            }
30425            self.generate_expression(&ordered.this)?;
30426            if ordered.desc {
30427                self.write_space();
30428                self.write_keyword("DESC");
30429            } else if ordered.explicit_asc {
30430                self.write_space();
30431                self.write_keyword("ASC");
30432            }
30433            if let Some(nulls_first) = ordered.nulls_first {
30434                // In Dremio, NULLS LAST is the default, so skip generating it
30435                let skip_nulls_last =
30436                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30437                if !skip_nulls_last {
30438                    self.write_space();
30439                    self.write_keyword("NULLS");
30440                    self.write_space();
30441                    if nulls_first {
30442                        self.write_keyword("FIRST");
30443                    } else {
30444                        self.write_keyword("LAST");
30445                    }
30446                }
30447            }
30448        }
30449        Ok(())
30450    }
30451
30452    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30453        // OUTPUT(model)
30454        self.write_keyword("OUTPUT");
30455        self.write("(");
30456        if self.config.pretty {
30457            self.indent_level += 1;
30458            self.write_newline();
30459            self.write_indent();
30460            self.generate_expression(&e.this)?;
30461            self.indent_level -= 1;
30462            self.write_newline();
30463        } else {
30464            self.generate_expression(&e.this)?;
30465        }
30466        self.write(")");
30467        Ok(())
30468    }
30469
30470    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30471        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30472        self.write_keyword("TRUNCATE");
30473        if let Some(this) = &e.this {
30474            self.write_space();
30475            self.generate_expression(this)?;
30476        }
30477        if e.with_count.is_some() {
30478            self.write_keyword(" WITH COUNT");
30479        } else {
30480            self.write_keyword(" WITHOUT COUNT");
30481        }
30482        Ok(())
30483    }
30484
30485    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30486        // Python: name(expressions)(params)
30487        self.generate_expression(&e.this)?;
30488        self.write("(");
30489        for (i, expr) in e.expressions.iter().enumerate() {
30490            if i > 0 {
30491                self.write(", ");
30492            }
30493            self.generate_expression(expr)?;
30494        }
30495        self.write(")(");
30496        for (i, param) in e.params.iter().enumerate() {
30497            if i > 0 {
30498                self.write(", ");
30499            }
30500            self.generate_expression(param)?;
30501        }
30502        self.write(")");
30503        Ok(())
30504    }
30505
30506    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30507        // PARSE_DATETIME(format, this) or similar
30508        self.write_keyword("PARSE_DATETIME");
30509        self.write("(");
30510        if let Some(format) = &e.format {
30511            self.write("'");
30512            self.write(format);
30513            self.write("', ");
30514        }
30515        self.generate_expression(&e.this)?;
30516        if let Some(zone) = &e.zone {
30517            self.write(", ");
30518            self.generate_expression(zone)?;
30519        }
30520        self.write(")");
30521        Ok(())
30522    }
30523
30524    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30525        // PARSE_IP(this, type, permissive)
30526        self.write_keyword("PARSE_IP");
30527        self.write("(");
30528        self.generate_expression(&e.this)?;
30529        if let Some(type_) = &e.type_ {
30530            self.write(", ");
30531            self.generate_expression(type_)?;
30532        }
30533        if let Some(permissive) = &e.permissive {
30534            self.write(", ");
30535            self.generate_expression(permissive)?;
30536        }
30537        self.write(")");
30538        Ok(())
30539    }
30540
30541    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30542        // PARSE_JSON(this, [expression])
30543        self.write_keyword("PARSE_JSON");
30544        self.write("(");
30545        self.generate_expression(&e.this)?;
30546        if let Some(expression) = &e.expression {
30547            self.write(", ");
30548            self.generate_expression(expression)?;
30549        }
30550        self.write(")");
30551        Ok(())
30552    }
30553
30554    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30555        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30556        self.write_keyword("PARSE_TIME");
30557        self.write("(");
30558        self.write(&format!("'{}'", e.format));
30559        self.write(", ");
30560        self.generate_expression(&e.this)?;
30561        self.write(")");
30562        Ok(())
30563    }
30564
30565    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30566        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30567        self.write_keyword("PARSE_URL");
30568        self.write("(");
30569        self.generate_expression(&e.this)?;
30570        if let Some(part) = &e.part_to_extract {
30571            self.write(", ");
30572            self.generate_expression(part)?;
30573        }
30574        if let Some(key) = &e.key {
30575            self.write(", ");
30576            self.generate_expression(key)?;
30577        }
30578        if let Some(permissive) = &e.permissive {
30579            self.write(", ");
30580            self.generate_expression(permissive)?;
30581        }
30582        self.write(")");
30583        Ok(())
30584    }
30585
30586    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30587        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30588        if e.subpartition {
30589            self.write_keyword("SUBPARTITION");
30590        } else {
30591            self.write_keyword("PARTITION");
30592        }
30593        self.write("(");
30594        for (i, expr) in e.expressions.iter().enumerate() {
30595            if i > 0 {
30596                self.write(", ");
30597            }
30598            self.generate_expression(expr)?;
30599        }
30600        self.write(")");
30601        Ok(())
30602    }
30603
30604    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30605        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30606        if let Some(this) = &e.this {
30607            if let Some(expression) = &e.expression {
30608                // WITH (MODULUS this, REMAINDER expression)
30609                self.write_keyword("WITH");
30610                self.write(" (");
30611                self.write_keyword("MODULUS");
30612                self.write_space();
30613                self.generate_expression(this)?;
30614                self.write(", ");
30615                self.write_keyword("REMAINDER");
30616                self.write_space();
30617                self.generate_expression(expression)?;
30618                self.write(")");
30619            } else {
30620                // IN (this) - this could be a list
30621                self.write_keyword("IN");
30622                self.write(" (");
30623                self.generate_partition_bound_values(this)?;
30624                self.write(")");
30625            }
30626        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30627            // FROM (from_expressions) TO (to_expressions)
30628            self.write_keyword("FROM");
30629            self.write(" (");
30630            self.generate_partition_bound_values(from)?;
30631            self.write(") ");
30632            self.write_keyword("TO");
30633            self.write(" (");
30634            self.generate_partition_bound_values(to)?;
30635            self.write(")");
30636        }
30637        Ok(())
30638    }
30639
30640    /// Generate partition bound values - handles Tuple expressions by outputting
30641    /// contents without wrapping parens (since caller provides the parens)
30642    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30643        if let Expression::Tuple(t) = expr {
30644            for (i, e) in t.expressions.iter().enumerate() {
30645                if i > 0 {
30646                    self.write(", ");
30647                }
30648                self.generate_expression(e)?;
30649            }
30650            Ok(())
30651        } else {
30652            self.generate_expression(expr)
30653        }
30654    }
30655
30656    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30657        // PARTITION BY LIST (partition_expressions) (create_expressions)
30658        self.write_keyword("PARTITION BY LIST");
30659        if let Some(partition_exprs) = &e.partition_expressions {
30660            self.write(" (");
30661            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30662            self.generate_doris_partition_expressions(partition_exprs)?;
30663            self.write(")");
30664        }
30665        if let Some(create_exprs) = &e.create_expressions {
30666            self.write(" (");
30667            // Unwrap Tuple for partition definitions
30668            self.generate_doris_partition_definitions(create_exprs)?;
30669            self.write(")");
30670        }
30671        Ok(())
30672    }
30673
30674    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30675        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30676        self.write_keyword("PARTITION BY RANGE");
30677        if let Some(partition_exprs) = &e.partition_expressions {
30678            self.write(" (");
30679            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30680            self.generate_doris_partition_expressions(partition_exprs)?;
30681            self.write(")");
30682        }
30683        if let Some(create_exprs) = &e.create_expressions {
30684            self.write(" (");
30685            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30686            self.generate_doris_partition_definitions(create_exprs)?;
30687            self.write(")");
30688        }
30689        Ok(())
30690    }
30691
30692    /// Generate Doris partition column expressions (unwrap Tuple)
30693    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30694        if let Expression::Tuple(t) = expr {
30695            for (i, e) in t.expressions.iter().enumerate() {
30696                if i > 0 {
30697                    self.write(", ");
30698                }
30699                self.generate_expression(e)?;
30700            }
30701        } else {
30702            self.generate_expression(expr)?;
30703        }
30704        Ok(())
30705    }
30706
30707    /// Generate Doris partition definitions (comma-separated Partition expressions)
30708    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30709        match expr {
30710            Expression::Tuple(t) => {
30711                // Multiple partitions, comma-separated
30712                for (i, part) in t.expressions.iter().enumerate() {
30713                    if i > 0 {
30714                        self.write(", ");
30715                    }
30716                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30717                    if let Expression::Partition(p) = part {
30718                        for (j, inner) in p.expressions.iter().enumerate() {
30719                            if j > 0 {
30720                                self.write(", ");
30721                            }
30722                            self.generate_expression(inner)?;
30723                        }
30724                    } else {
30725                        self.generate_expression(part)?;
30726                    }
30727                }
30728            }
30729            Expression::PartitionByRangePropertyDynamic(_) => {
30730                // Dynamic partition - FROM/TO/INTERVAL
30731                self.generate_expression(expr)?;
30732            }
30733            _ => {
30734                self.generate_expression(expr)?;
30735            }
30736        }
30737        Ok(())
30738    }
30739
30740    fn generate_partition_by_range_property_dynamic(
30741        &mut self,
30742        e: &PartitionByRangePropertyDynamic,
30743    ) -> Result<()> {
30744        if e.use_start_end {
30745            // StarRocks: START ('val') END ('val') EVERY (expr)
30746            if let Some(start) = &e.start {
30747                self.write_keyword("START");
30748                self.write(" (");
30749                self.generate_expression(start)?;
30750                self.write(")");
30751            }
30752            if let Some(end) = &e.end {
30753                self.write_space();
30754                self.write_keyword("END");
30755                self.write(" (");
30756                self.generate_expression(end)?;
30757                self.write(")");
30758            }
30759            if let Some(every) = &e.every {
30760                self.write_space();
30761                self.write_keyword("EVERY");
30762                self.write(" (");
30763                // Use unquoted interval format for StarRocks
30764                self.generate_doris_interval(every)?;
30765                self.write(")");
30766            }
30767        } else {
30768            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30769            if let Some(start) = &e.start {
30770                self.write_keyword("FROM");
30771                self.write(" (");
30772                self.generate_expression(start)?;
30773                self.write(")");
30774            }
30775            if let Some(end) = &e.end {
30776                self.write_space();
30777                self.write_keyword("TO");
30778                self.write(" (");
30779                self.generate_expression(end)?;
30780                self.write(")");
30781            }
30782            if let Some(every) = &e.every {
30783                self.write_space();
30784                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30785                self.generate_doris_interval(every)?;
30786            }
30787        }
30788        Ok(())
30789    }
30790
30791    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30792    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30793        if let Expression::Interval(interval) = expr {
30794            self.write_keyword("INTERVAL");
30795            if let Some(ref value) = interval.this {
30796                self.write_space();
30797                // If the value is a string literal that looks like a number,
30798                // output it without quotes (matching Python sqlglot's
30799                // partitionbyrangepropertydynamic_sql which converts back to number)
30800                match value {
30801                    Expression::Literal(Literal::String(s))
30802                        if s.chars()
30803                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30804                            && !s.is_empty() =>
30805                    {
30806                        self.write(s);
30807                    }
30808                    _ => {
30809                        self.generate_expression(value)?;
30810                    }
30811                }
30812            }
30813            if let Some(ref unit_spec) = interval.unit {
30814                self.write_space();
30815                self.write_interval_unit_spec(unit_spec)?;
30816            }
30817            Ok(())
30818        } else {
30819            self.generate_expression(expr)
30820        }
30821    }
30822
30823    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30824        // TRUNCATE(expression, this)
30825        self.write_keyword("TRUNCATE");
30826        self.write("(");
30827        self.generate_expression(&e.expression)?;
30828        self.write(", ");
30829        self.generate_expression(&e.this)?;
30830        self.write(")");
30831        Ok(())
30832    }
30833
30834    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30835        // Doris: PARTITION name VALUES IN (val1, val2)
30836        self.write_keyword("PARTITION");
30837        self.write_space();
30838        self.generate_expression(&e.this)?;
30839        self.write_space();
30840        self.write_keyword("VALUES IN");
30841        self.write(" (");
30842        for (i, expr) in e.expressions.iter().enumerate() {
30843            if i > 0 {
30844                self.write(", ");
30845            }
30846            self.generate_expression(expr)?;
30847        }
30848        self.write(")");
30849        Ok(())
30850    }
30851
30852    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30853        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30854        // TSQL ranges have no expressions and just use `this TO expression`
30855        if e.expressions.is_empty() && e.expression.is_some() {
30856            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30857            self.generate_expression(&e.this)?;
30858            self.write_space();
30859            self.write_keyword("TO");
30860            self.write_space();
30861            self.generate_expression(e.expression.as_ref().unwrap())?;
30862            return Ok(());
30863        }
30864
30865        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30866        self.write_keyword("PARTITION");
30867        self.write_space();
30868        self.generate_expression(&e.this)?;
30869        self.write_space();
30870
30871        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30872        if e.expressions.len() == 1 {
30873            // Single value: VALUES LESS THAN (val)
30874            self.write_keyword("VALUES LESS THAN");
30875            self.write(" (");
30876            self.generate_expression(&e.expressions[0])?;
30877            self.write(")");
30878        } else if !e.expressions.is_empty() {
30879            // Multiple values with Tuple: VALUES [(val1), (val2))
30880            self.write_keyword("VALUES");
30881            self.write(" [");
30882            for (i, expr) in e.expressions.iter().enumerate() {
30883                if i > 0 {
30884                    self.write(", ");
30885                }
30886                // If the expr is a Tuple, generate its contents wrapped in parens
30887                if let Expression::Tuple(t) = expr {
30888                    self.write("(");
30889                    for (j, inner) in t.expressions.iter().enumerate() {
30890                        if j > 0 {
30891                            self.write(", ");
30892                        }
30893                        self.generate_expression(inner)?;
30894                    }
30895                    self.write(")");
30896                } else {
30897                    self.write("(");
30898                    self.generate_expression(expr)?;
30899                    self.write(")");
30900                }
30901            }
30902            self.write(")");
30903        }
30904        Ok(())
30905    }
30906
30907    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30908        // BUCKET(this, expression)
30909        self.write_keyword("BUCKET");
30910        self.write("(");
30911        self.generate_expression(&e.this)?;
30912        self.write(", ");
30913        self.generate_expression(&e.expression)?;
30914        self.write(")");
30915        Ok(())
30916    }
30917
30918    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
30919        // BigQuery table property: PARTITION BY expression [, expression ...]
30920        self.write_keyword("PARTITION BY");
30921        self.write_space();
30922        for (i, expr) in e.expressions.iter().enumerate() {
30923            if i > 0 {
30924                self.write(", ");
30925            }
30926            self.generate_expression(expr)?;
30927        }
30928        Ok(())
30929    }
30930
30931    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30932        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30933        if matches!(
30934            self.config.dialect,
30935            Some(crate::dialects::DialectType::Teradata)
30936                | Some(crate::dialects::DialectType::ClickHouse)
30937        ) {
30938            self.write_keyword("PARTITION BY");
30939        } else {
30940            self.write_keyword("PARTITIONED BY");
30941        }
30942        self.write_space();
30943        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30944        if self.config.pretty {
30945            if let Expression::Tuple(ref tuple) = *e.this {
30946                self.write("(");
30947                self.write_newline();
30948                self.indent_level += 1;
30949                for (i, expr) in tuple.expressions.iter().enumerate() {
30950                    if i > 0 {
30951                        self.write(",");
30952                        self.write_newline();
30953                    }
30954                    self.write_indent();
30955                    self.generate_expression(expr)?;
30956                }
30957                self.indent_level -= 1;
30958                self.write_newline();
30959                self.write(")");
30960            } else {
30961                self.generate_expression(&e.this)?;
30962            }
30963        } else {
30964            self.generate_expression(&e.this)?;
30965        }
30966        Ok(())
30967    }
30968
30969    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30970        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30971        self.write_keyword("PARTITION OF");
30972        self.write_space();
30973        self.generate_expression(&e.this)?;
30974        // Check if expression is a PartitionBoundSpec
30975        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30976            self.write_space();
30977            self.write_keyword("FOR VALUES");
30978            self.write_space();
30979            self.generate_expression(&e.expression)?;
30980        } else {
30981            self.write_space();
30982            self.write_keyword("DEFAULT");
30983        }
30984        Ok(())
30985    }
30986
30987    fn generate_period_for_system_time_constraint(
30988        &mut self,
30989        e: &PeriodForSystemTimeConstraint,
30990    ) -> Result<()> {
30991        // PERIOD FOR SYSTEM_TIME (this, expression)
30992        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30993        self.write(" (");
30994        self.generate_expression(&e.this)?;
30995        self.write(", ");
30996        self.generate_expression(&e.expression)?;
30997        self.write(")");
30998        Ok(())
30999    }
31000
31001    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
31002        // value AS alias
31003        // The alias can be an identifier or an expression (e.g., string concatenation)
31004        self.generate_expression(&e.this)?;
31005        self.write_space();
31006        self.write_keyword("AS");
31007        self.write_space();
31008        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
31009        if self.config.unpivot_aliases_are_identifiers {
31010            match &e.alias {
31011                Expression::Literal(Literal::String(s)) => {
31012                    // Convert string literal to identifier
31013                    self.generate_identifier(&Identifier::new(s.clone()))?;
31014                }
31015                Expression::Literal(Literal::Number(n)) => {
31016                    // Convert number literal to quoted identifier
31017                    let mut id = Identifier::new(n.clone());
31018                    id.quoted = true;
31019                    self.generate_identifier(&id)?;
31020                }
31021                other => {
31022                    self.generate_expression(other)?;
31023                }
31024            }
31025        } else {
31026            self.generate_expression(&e.alias)?;
31027        }
31028        Ok(())
31029    }
31030
31031    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
31032        // ANY or ANY [expression]
31033        self.write_keyword("ANY");
31034        if let Some(this) = &e.this {
31035            self.write_space();
31036            self.generate_expression(this)?;
31037        }
31038        Ok(())
31039    }
31040
31041    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
31042        // ML.PREDICT(MODEL this, expression, [params_struct])
31043        self.write_keyword("ML.PREDICT");
31044        self.write("(");
31045        self.write_keyword("MODEL");
31046        self.write_space();
31047        self.generate_expression(&e.this)?;
31048        self.write(", ");
31049        self.generate_expression(&e.expression)?;
31050        if let Some(params) = &e.params_struct {
31051            self.write(", ");
31052            self.generate_expression(params)?;
31053        }
31054        self.write(")");
31055        Ok(())
31056    }
31057
31058    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
31059        // PREVIOUS_DAY(this, expression)
31060        self.write_keyword("PREVIOUS_DAY");
31061        self.write("(");
31062        self.generate_expression(&e.this)?;
31063        self.write(", ");
31064        self.generate_expression(&e.expression)?;
31065        self.write(")");
31066        Ok(())
31067    }
31068
31069    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
31070        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
31071        self.write_keyword("PRIMARY KEY");
31072        if let Some(name) = &e.this {
31073            self.write_space();
31074            self.generate_expression(name)?;
31075        }
31076        if !e.expressions.is_empty() {
31077            self.write(" (");
31078            for (i, expr) in e.expressions.iter().enumerate() {
31079                if i > 0 {
31080                    self.write(", ");
31081                }
31082                self.generate_expression(expr)?;
31083            }
31084            self.write(")");
31085        }
31086        if let Some(include) = &e.include {
31087            self.write_space();
31088            self.generate_expression(include)?;
31089        }
31090        if !e.options.is_empty() {
31091            self.write_space();
31092            for (i, opt) in e.options.iter().enumerate() {
31093                if i > 0 {
31094                    self.write_space();
31095                }
31096                self.generate_expression(opt)?;
31097            }
31098        }
31099        Ok(())
31100    }
31101
31102    fn generate_primary_key_column_constraint(
31103        &mut self,
31104        _e: &PrimaryKeyColumnConstraint,
31105    ) -> Result<()> {
31106        // PRIMARY KEY constraint at column level
31107        self.write_keyword("PRIMARY KEY");
31108        Ok(())
31109    }
31110
31111    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
31112        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
31113        self.write_keyword("PATH");
31114        self.write_space();
31115        self.generate_expression(&e.this)?;
31116        Ok(())
31117    }
31118
31119    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
31120        // PROJECTION this (expression)
31121        self.write_keyword("PROJECTION");
31122        self.write_space();
31123        self.generate_expression(&e.this)?;
31124        self.write(" (");
31125        self.generate_expression(&e.expression)?;
31126        self.write(")");
31127        Ok(())
31128    }
31129
31130    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
31131        // Properties list
31132        for (i, prop) in e.expressions.iter().enumerate() {
31133            if i > 0 {
31134                self.write(", ");
31135            }
31136            self.generate_expression(prop)?;
31137        }
31138        Ok(())
31139    }
31140
31141    fn generate_property(&mut self, e: &Property) -> Result<()> {
31142        // name=value
31143        self.generate_expression(&e.this)?;
31144        if let Some(value) = &e.value {
31145            self.write("=");
31146            self.generate_expression(value)?;
31147        }
31148        Ok(())
31149    }
31150
31151    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
31152        self.write_keyword("OPTIONS");
31153        if e.entries.is_empty() {
31154            self.write(" ()");
31155            return Ok(());
31156        }
31157
31158        if self.config.pretty {
31159            self.write(" (");
31160            self.write_newline();
31161            self.indent_level += 1;
31162            for (i, entry) in e.entries.iter().enumerate() {
31163                if i > 0 {
31164                    self.write(",");
31165                    self.write_newline();
31166                }
31167                self.write_indent();
31168                self.generate_identifier(&entry.key)?;
31169                self.write("=");
31170                self.generate_expression(&entry.value)?;
31171            }
31172            self.indent_level -= 1;
31173            self.write_newline();
31174            self.write(")");
31175        } else {
31176            self.write(" (");
31177            for (i, entry) in e.entries.iter().enumerate() {
31178                if i > 0 {
31179                    self.write(", ");
31180                }
31181                self.generate_identifier(&entry.key)?;
31182                self.write("=");
31183                self.generate_expression(&entry.value)?;
31184            }
31185            self.write(")");
31186        }
31187        Ok(())
31188    }
31189
31190    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
31191    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
31192        self.write_keyword("OPTIONS");
31193        self.write(" (");
31194        for (i, opt) in options.iter().enumerate() {
31195            if i > 0 {
31196                self.write(", ");
31197            }
31198            self.generate_option_expression(opt)?;
31199        }
31200        self.write(")");
31201        Ok(())
31202    }
31203
31204    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31205    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31206        self.write_keyword("PROPERTIES");
31207        self.write(" (");
31208        for (i, prop) in properties.iter().enumerate() {
31209            if i > 0 {
31210                self.write(", ");
31211            }
31212            self.generate_option_expression(prop)?;
31213        }
31214        self.write(")");
31215        Ok(())
31216    }
31217
31218    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31219    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31220        self.write_keyword("ENVIRONMENT");
31221        self.write(" (");
31222        for (i, env_item) in environment.iter().enumerate() {
31223            if i > 0 {
31224                self.write(", ");
31225            }
31226            self.generate_environment_expression(env_item)?;
31227        }
31228        self.write(")");
31229        Ok(())
31230    }
31231
31232    /// Generate an environment expression with spaces around =
31233    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31234        match expr {
31235            Expression::Eq(eq) => {
31236                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31237                self.generate_expression(&eq.left)?;
31238                self.write(" = ");
31239                self.generate_expression(&eq.right)?;
31240                Ok(())
31241            }
31242            _ => self.generate_expression(expr),
31243        }
31244    }
31245
31246    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31247    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31248        self.write_keyword("TBLPROPERTIES");
31249        if self.config.pretty {
31250            self.write(" (");
31251            self.write_newline();
31252            self.indent_level += 1;
31253            for (i, opt) in options.iter().enumerate() {
31254                if i > 0 {
31255                    self.write(",");
31256                    self.write_newline();
31257                }
31258                self.write_indent();
31259                self.generate_option_expression(opt)?;
31260            }
31261            self.indent_level -= 1;
31262            self.write_newline();
31263            self.write(")");
31264        } else {
31265            self.write(" (");
31266            for (i, opt) in options.iter().enumerate() {
31267                if i > 0 {
31268                    self.write(", ");
31269                }
31270                self.generate_option_expression(opt)?;
31271            }
31272            self.write(")");
31273        }
31274        Ok(())
31275    }
31276
31277    /// Generate an option expression without spaces around =
31278    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31279        match expr {
31280            Expression::Eq(eq) => {
31281                // Generate key=value without spaces
31282                self.generate_expression(&eq.left)?;
31283                self.write("=");
31284                self.generate_expression(&eq.right)?;
31285                Ok(())
31286            }
31287            _ => self.generate_expression(expr),
31288        }
31289    }
31290
31291    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31292        // Just output the name
31293        self.generate_expression(&e.this)?;
31294        Ok(())
31295    }
31296
31297    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31298        // PUT source_file @stage [options]
31299        self.write_keyword("PUT");
31300        self.write_space();
31301
31302        // Source file path - preserve original quoting
31303        if e.source_quoted {
31304            self.write("'");
31305            self.write(&e.source);
31306            self.write("'");
31307        } else {
31308            self.write(&e.source);
31309        }
31310
31311        self.write_space();
31312
31313        // Target stage reference - output the string directly (includes @)
31314        if let Expression::Literal(Literal::String(s)) = &e.target {
31315            self.write(s);
31316        } else {
31317            self.generate_expression(&e.target)?;
31318        }
31319
31320        // Optional parameters: KEY=VALUE
31321        for param in &e.params {
31322            self.write_space();
31323            self.write(&param.name);
31324            if let Some(ref value) = param.value {
31325                self.write("=");
31326                self.generate_expression(value)?;
31327            }
31328        }
31329
31330        Ok(())
31331    }
31332
31333    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31334        // QUANTILE(this, quantile)
31335        self.write_keyword("QUANTILE");
31336        self.write("(");
31337        self.generate_expression(&e.this)?;
31338        if let Some(quantile) = &e.quantile {
31339            self.write(", ");
31340            self.generate_expression(quantile)?;
31341        }
31342        self.write(")");
31343        Ok(())
31344    }
31345
31346    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31347        // QUERY_BAND = this [UPDATE] [FOR scope]
31348        if matches!(
31349            self.config.dialect,
31350            Some(crate::dialects::DialectType::Teradata)
31351        ) {
31352            self.write_keyword("SET");
31353            self.write_space();
31354        }
31355        self.write_keyword("QUERY_BAND");
31356        self.write(" = ");
31357        self.generate_expression(&e.this)?;
31358        if e.update.is_some() {
31359            self.write_space();
31360            self.write_keyword("UPDATE");
31361        }
31362        if let Some(scope) = &e.scope {
31363            self.write_space();
31364            self.write_keyword("FOR");
31365            self.write_space();
31366            self.generate_expression(scope)?;
31367        }
31368        Ok(())
31369    }
31370
31371    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31372        // this = expression
31373        self.generate_expression(&e.this)?;
31374        if let Some(expression) = &e.expression {
31375            self.write(" = ");
31376            self.generate_expression(expression)?;
31377        }
31378        Ok(())
31379    }
31380
31381    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31382        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31383        self.write_keyword("TRANSFORM");
31384        self.write("(");
31385        for (i, expr) in e.expressions.iter().enumerate() {
31386            if i > 0 {
31387                self.write(", ");
31388            }
31389            self.generate_expression(expr)?;
31390        }
31391        self.write(")");
31392        if let Some(row_format_before) = &e.row_format_before {
31393            self.write_space();
31394            self.generate_expression(row_format_before)?;
31395        }
31396        if let Some(record_writer) = &e.record_writer {
31397            self.write_space();
31398            self.write_keyword("RECORDWRITER");
31399            self.write_space();
31400            self.generate_expression(record_writer)?;
31401        }
31402        if let Some(command_script) = &e.command_script {
31403            self.write_space();
31404            self.write_keyword("USING");
31405            self.write_space();
31406            self.generate_expression(command_script)?;
31407        }
31408        if let Some(schema) = &e.schema {
31409            self.write_space();
31410            self.write_keyword("AS");
31411            self.write_space();
31412            self.generate_expression(schema)?;
31413        }
31414        if let Some(row_format_after) = &e.row_format_after {
31415            self.write_space();
31416            self.generate_expression(row_format_after)?;
31417        }
31418        if let Some(record_reader) = &e.record_reader {
31419            self.write_space();
31420            self.write_keyword("RECORDREADER");
31421            self.write_space();
31422            self.generate_expression(record_reader)?;
31423        }
31424        Ok(())
31425    }
31426
31427    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31428        // RANDN([seed])
31429        self.write_keyword("RANDN");
31430        self.write("(");
31431        if let Some(this) = &e.this {
31432            self.generate_expression(this)?;
31433        }
31434        self.write(")");
31435        Ok(())
31436    }
31437
31438    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31439        // RANDSTR(this, [generator])
31440        self.write_keyword("RANDSTR");
31441        self.write("(");
31442        self.generate_expression(&e.this)?;
31443        if let Some(generator) = &e.generator {
31444            self.write(", ");
31445            self.generate_expression(generator)?;
31446        }
31447        self.write(")");
31448        Ok(())
31449    }
31450
31451    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31452        // RANGE_BUCKET(this, expression)
31453        self.write_keyword("RANGE_BUCKET");
31454        self.write("(");
31455        self.generate_expression(&e.this)?;
31456        self.write(", ");
31457        self.generate_expression(&e.expression)?;
31458        self.write(")");
31459        Ok(())
31460    }
31461
31462    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31463        // RANGE_N(this BETWEEN expressions [EACH each])
31464        self.write_keyword("RANGE_N");
31465        self.write("(");
31466        self.generate_expression(&e.this)?;
31467        self.write_space();
31468        self.write_keyword("BETWEEN");
31469        self.write_space();
31470        for (i, expr) in e.expressions.iter().enumerate() {
31471            if i > 0 {
31472                self.write(", ");
31473            }
31474            self.generate_expression(expr)?;
31475        }
31476        if let Some(each) = &e.each {
31477            self.write_space();
31478            self.write_keyword("EACH");
31479            self.write_space();
31480            self.generate_expression(each)?;
31481        }
31482        self.write(")");
31483        Ok(())
31484    }
31485
31486    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31487        // READ_CSV(this, expressions...)
31488        self.write_keyword("READ_CSV");
31489        self.write("(");
31490        self.generate_expression(&e.this)?;
31491        for expr in &e.expressions {
31492            self.write(", ");
31493            self.generate_expression(expr)?;
31494        }
31495        self.write(")");
31496        Ok(())
31497    }
31498
31499    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31500        // READ_PARQUET(expressions...)
31501        self.write_keyword("READ_PARQUET");
31502        self.write("(");
31503        for (i, expr) in e.expressions.iter().enumerate() {
31504            if i > 0 {
31505                self.write(", ");
31506            }
31507            self.generate_expression(expr)?;
31508        }
31509        self.write(")");
31510        Ok(())
31511    }
31512
31513    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31514        // SEARCH kind FIRST BY this SET expression [USING using]
31515        // or CYCLE this SET expression [USING using]
31516        if e.kind == "CYCLE" {
31517            self.write_keyword("CYCLE");
31518        } else {
31519            self.write_keyword("SEARCH");
31520            self.write_space();
31521            self.write(&e.kind);
31522            self.write_space();
31523            self.write_keyword("FIRST BY");
31524        }
31525        self.write_space();
31526        self.generate_expression(&e.this)?;
31527        self.write_space();
31528        self.write_keyword("SET");
31529        self.write_space();
31530        self.generate_expression(&e.expression)?;
31531        if let Some(using) = &e.using {
31532            self.write_space();
31533            self.write_keyword("USING");
31534            self.write_space();
31535            self.generate_expression(using)?;
31536        }
31537        Ok(())
31538    }
31539
31540    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31541        // REDUCE(this, initial, merge, [finish])
31542        self.write_keyword("REDUCE");
31543        self.write("(");
31544        self.generate_expression(&e.this)?;
31545        if let Some(initial) = &e.initial {
31546            self.write(", ");
31547            self.generate_expression(initial)?;
31548        }
31549        if let Some(merge) = &e.merge {
31550            self.write(", ");
31551            self.generate_expression(merge)?;
31552        }
31553        if let Some(finish) = &e.finish {
31554            self.write(", ");
31555            self.generate_expression(finish)?;
31556        }
31557        self.write(")");
31558        Ok(())
31559    }
31560
31561    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31562        // REFERENCES this (expressions) [options]
31563        self.write_keyword("REFERENCES");
31564        self.write_space();
31565        self.generate_expression(&e.this)?;
31566        if !e.expressions.is_empty() {
31567            self.write(" (");
31568            for (i, expr) in e.expressions.iter().enumerate() {
31569                if i > 0 {
31570                    self.write(", ");
31571                }
31572                self.generate_expression(expr)?;
31573            }
31574            self.write(")");
31575        }
31576        for opt in &e.options {
31577            self.write_space();
31578            self.generate_expression(opt)?;
31579        }
31580        Ok(())
31581    }
31582
31583    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31584        // REFRESH [kind] this
31585        self.write_keyword("REFRESH");
31586        if !e.kind.is_empty() {
31587            self.write_space();
31588            self.write_keyword(&e.kind);
31589        }
31590        self.write_space();
31591        self.generate_expression(&e.this)?;
31592        Ok(())
31593    }
31594
31595    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31596        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31597        self.write_keyword("REFRESH");
31598        self.write_space();
31599        self.write_keyword(&e.method);
31600
31601        if let Some(ref kind) = e.kind {
31602            self.write_space();
31603            self.write_keyword("ON");
31604            self.write_space();
31605            self.write_keyword(kind);
31606
31607            // EVERY n UNIT
31608            if let Some(ref every) = e.every {
31609                self.write_space();
31610                self.write_keyword("EVERY");
31611                self.write_space();
31612                self.generate_expression(every)?;
31613                if let Some(ref unit) = e.unit {
31614                    self.write_space();
31615                    self.write_keyword(unit);
31616                }
31617            }
31618
31619            // STARTS 'datetime'
31620            if let Some(ref starts) = e.starts {
31621                self.write_space();
31622                self.write_keyword("STARTS");
31623                self.write_space();
31624                self.generate_expression(starts)?;
31625            }
31626        }
31627        Ok(())
31628    }
31629
31630    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31631        // REGEXP_COUNT(this, expression, position, parameters)
31632        self.write_keyword("REGEXP_COUNT");
31633        self.write("(");
31634        self.generate_expression(&e.this)?;
31635        self.write(", ");
31636        self.generate_expression(&e.expression)?;
31637        if let Some(position) = &e.position {
31638            self.write(", ");
31639            self.generate_expression(position)?;
31640        }
31641        if let Some(parameters) = &e.parameters {
31642            self.write(", ");
31643            self.generate_expression(parameters)?;
31644        }
31645        self.write(")");
31646        Ok(())
31647    }
31648
31649    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31650        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31651        self.write_keyword("REGEXP_EXTRACT_ALL");
31652        self.write("(");
31653        self.generate_expression(&e.this)?;
31654        self.write(", ");
31655        self.generate_expression(&e.expression)?;
31656        if let Some(group) = &e.group {
31657            self.write(", ");
31658            self.generate_expression(group)?;
31659        }
31660        self.write(")");
31661        Ok(())
31662    }
31663
31664    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31665        // REGEXP_FULL_MATCH(this, expression)
31666        self.write_keyword("REGEXP_FULL_MATCH");
31667        self.write("(");
31668        self.generate_expression(&e.this)?;
31669        self.write(", ");
31670        self.generate_expression(&e.expression)?;
31671        self.write(")");
31672        Ok(())
31673    }
31674
31675    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31676        use crate::dialects::DialectType;
31677        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31678        if matches!(
31679            self.config.dialect,
31680            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31681        ) && e.flag.is_none()
31682        {
31683            self.generate_expression(&e.this)?;
31684            self.write(" ~* ");
31685            self.generate_expression(&e.expression)?;
31686        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31687            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31688            self.write_keyword("REGEXP_LIKE");
31689            self.write("(");
31690            self.generate_expression(&e.this)?;
31691            self.write(", ");
31692            self.generate_expression(&e.expression)?;
31693            self.write(", ");
31694            if let Some(flag) = &e.flag {
31695                self.generate_expression(flag)?;
31696            } else {
31697                self.write("'i'");
31698            }
31699            self.write(")");
31700        } else {
31701            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31702            self.generate_expression(&e.this)?;
31703            self.write_space();
31704            self.write_keyword("REGEXP_ILIKE");
31705            self.write_space();
31706            self.generate_expression(&e.expression)?;
31707            if let Some(flag) = &e.flag {
31708                self.write(", ");
31709                self.generate_expression(flag)?;
31710            }
31711        }
31712        Ok(())
31713    }
31714
31715    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31716        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31717        self.write_keyword("REGEXP_INSTR");
31718        self.write("(");
31719        self.generate_expression(&e.this)?;
31720        self.write(", ");
31721        self.generate_expression(&e.expression)?;
31722        if let Some(position) = &e.position {
31723            self.write(", ");
31724            self.generate_expression(position)?;
31725        }
31726        if let Some(occurrence) = &e.occurrence {
31727            self.write(", ");
31728            self.generate_expression(occurrence)?;
31729        }
31730        if let Some(option) = &e.option {
31731            self.write(", ");
31732            self.generate_expression(option)?;
31733        }
31734        if let Some(parameters) = &e.parameters {
31735            self.write(", ");
31736            self.generate_expression(parameters)?;
31737        }
31738        if let Some(group) = &e.group {
31739            self.write(", ");
31740            self.generate_expression(group)?;
31741        }
31742        self.write(")");
31743        Ok(())
31744    }
31745
31746    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31747        // REGEXP_SPLIT(this, expression, limit)
31748        self.write_keyword("REGEXP_SPLIT");
31749        self.write("(");
31750        self.generate_expression(&e.this)?;
31751        self.write(", ");
31752        self.generate_expression(&e.expression)?;
31753        if let Some(limit) = &e.limit {
31754            self.write(", ");
31755            self.generate_expression(limit)?;
31756        }
31757        self.write(")");
31758        Ok(())
31759    }
31760
31761    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31762        // REGR_AVGX(this, expression)
31763        self.write_keyword("REGR_AVGX");
31764        self.write("(");
31765        self.generate_expression(&e.this)?;
31766        self.write(", ");
31767        self.generate_expression(&e.expression)?;
31768        self.write(")");
31769        Ok(())
31770    }
31771
31772    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31773        // REGR_AVGY(this, expression)
31774        self.write_keyword("REGR_AVGY");
31775        self.write("(");
31776        self.generate_expression(&e.this)?;
31777        self.write(", ");
31778        self.generate_expression(&e.expression)?;
31779        self.write(")");
31780        Ok(())
31781    }
31782
31783    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31784        // REGR_COUNT(this, expression)
31785        self.write_keyword("REGR_COUNT");
31786        self.write("(");
31787        self.generate_expression(&e.this)?;
31788        self.write(", ");
31789        self.generate_expression(&e.expression)?;
31790        self.write(")");
31791        Ok(())
31792    }
31793
31794    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31795        // REGR_INTERCEPT(this, expression)
31796        self.write_keyword("REGR_INTERCEPT");
31797        self.write("(");
31798        self.generate_expression(&e.this)?;
31799        self.write(", ");
31800        self.generate_expression(&e.expression)?;
31801        self.write(")");
31802        Ok(())
31803    }
31804
31805    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31806        // REGR_R2(this, expression)
31807        self.write_keyword("REGR_R2");
31808        self.write("(");
31809        self.generate_expression(&e.this)?;
31810        self.write(", ");
31811        self.generate_expression(&e.expression)?;
31812        self.write(")");
31813        Ok(())
31814    }
31815
31816    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31817        // REGR_SLOPE(this, expression)
31818        self.write_keyword("REGR_SLOPE");
31819        self.write("(");
31820        self.generate_expression(&e.this)?;
31821        self.write(", ");
31822        self.generate_expression(&e.expression)?;
31823        self.write(")");
31824        Ok(())
31825    }
31826
31827    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31828        // REGR_SXX(this, expression)
31829        self.write_keyword("REGR_SXX");
31830        self.write("(");
31831        self.generate_expression(&e.this)?;
31832        self.write(", ");
31833        self.generate_expression(&e.expression)?;
31834        self.write(")");
31835        Ok(())
31836    }
31837
31838    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31839        // REGR_SXY(this, expression)
31840        self.write_keyword("REGR_SXY");
31841        self.write("(");
31842        self.generate_expression(&e.this)?;
31843        self.write(", ");
31844        self.generate_expression(&e.expression)?;
31845        self.write(")");
31846        Ok(())
31847    }
31848
31849    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31850        // REGR_SYY(this, expression)
31851        self.write_keyword("REGR_SYY");
31852        self.write("(");
31853        self.generate_expression(&e.this)?;
31854        self.write(", ");
31855        self.generate_expression(&e.expression)?;
31856        self.write(")");
31857        Ok(())
31858    }
31859
31860    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31861        // REGR_VALX(this, expression)
31862        self.write_keyword("REGR_VALX");
31863        self.write("(");
31864        self.generate_expression(&e.this)?;
31865        self.write(", ");
31866        self.generate_expression(&e.expression)?;
31867        self.write(")");
31868        Ok(())
31869    }
31870
31871    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31872        // REGR_VALY(this, expression)
31873        self.write_keyword("REGR_VALY");
31874        self.write("(");
31875        self.generate_expression(&e.this)?;
31876        self.write(", ");
31877        self.generate_expression(&e.expression)?;
31878        self.write(")");
31879        Ok(())
31880    }
31881
31882    fn generate_remote_with_connection_model_property(
31883        &mut self,
31884        e: &RemoteWithConnectionModelProperty,
31885    ) -> Result<()> {
31886        // REMOTE WITH CONNECTION this
31887        self.write_keyword("REMOTE WITH CONNECTION");
31888        self.write_space();
31889        self.generate_expression(&e.this)?;
31890        Ok(())
31891    }
31892
31893    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31894        // RENAME COLUMN [IF EXISTS] this TO new_name
31895        self.write_keyword("RENAME COLUMN");
31896        if e.exists {
31897            self.write_space();
31898            self.write_keyword("IF EXISTS");
31899        }
31900        self.write_space();
31901        self.generate_expression(&e.this)?;
31902        if let Some(to) = &e.to {
31903            self.write_space();
31904            self.write_keyword("TO");
31905            self.write_space();
31906            self.generate_expression(to)?;
31907        }
31908        Ok(())
31909    }
31910
31911    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31912        // REPLACE PARTITION expression [FROM source]
31913        self.write_keyword("REPLACE PARTITION");
31914        self.write_space();
31915        self.generate_expression(&e.expression)?;
31916        if let Some(source) = &e.source {
31917            self.write_space();
31918            self.write_keyword("FROM");
31919            self.write_space();
31920            self.generate_expression(source)?;
31921        }
31922        Ok(())
31923    }
31924
31925    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31926        // RETURNING expressions [INTO into]
31927        // TSQL and Fabric use OUTPUT instead of RETURNING
31928        let keyword = match self.config.dialect {
31929            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31930            _ => "RETURNING",
31931        };
31932        self.write_keyword(keyword);
31933        self.write_space();
31934        for (i, expr) in e.expressions.iter().enumerate() {
31935            if i > 0 {
31936                self.write(", ");
31937            }
31938            self.generate_expression(expr)?;
31939        }
31940        if let Some(into) = &e.into {
31941            self.write_space();
31942            self.write_keyword("INTO");
31943            self.write_space();
31944            self.generate_expression(into)?;
31945        }
31946        Ok(())
31947    }
31948
31949    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31950        // OUTPUT expressions [INTO into_table]
31951        self.write_space();
31952        self.write_keyword("OUTPUT");
31953        self.write_space();
31954        for (i, expr) in output.columns.iter().enumerate() {
31955            if i > 0 {
31956                self.write(", ");
31957            }
31958            self.generate_expression(expr)?;
31959        }
31960        if let Some(into_table) = &output.into_table {
31961            self.write_space();
31962            self.write_keyword("INTO");
31963            self.write_space();
31964            self.generate_expression(into_table)?;
31965        }
31966        Ok(())
31967    }
31968
31969    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31970        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31971        self.write_keyword("RETURNS");
31972        if e.is_table.is_some() {
31973            self.write_space();
31974            self.write_keyword("TABLE");
31975        }
31976        if let Some(table) = &e.table {
31977            self.write_space();
31978            self.generate_expression(table)?;
31979        } else if let Some(this) = &e.this {
31980            self.write_space();
31981            self.generate_expression(this)?;
31982        }
31983        if e.null.is_some() {
31984            self.write_space();
31985            self.write_keyword("NULL ON NULL INPUT");
31986        }
31987        Ok(())
31988    }
31989
31990    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31991        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31992        self.write_keyword("ROLLBACK");
31993
31994        // TSQL always uses ROLLBACK TRANSACTION
31995        if e.this.is_none()
31996            && matches!(
31997                self.config.dialect,
31998                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31999            )
32000        {
32001            self.write_space();
32002            self.write_keyword("TRANSACTION");
32003        }
32004
32005        // Check if this has TRANSACTION keyword or transaction name
32006        if let Some(this) = &e.this {
32007            // Check if it's just the "TRANSACTION" marker or an actual transaction name
32008            let is_transaction_marker = matches!(
32009                this.as_ref(),
32010                Expression::Identifier(id) if id.name == "TRANSACTION"
32011            );
32012
32013            self.write_space();
32014            self.write_keyword("TRANSACTION");
32015
32016            // If it's a real transaction name, output it
32017            if !is_transaction_marker {
32018                self.write_space();
32019                self.generate_expression(this)?;
32020            }
32021        }
32022
32023        // Output TO savepoint
32024        if let Some(savepoint) = &e.savepoint {
32025            self.write_space();
32026            self.write_keyword("TO");
32027            self.write_space();
32028            self.generate_expression(savepoint)?;
32029        }
32030        Ok(())
32031    }
32032
32033    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
32034        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
32035        if e.expressions.is_empty() {
32036            self.write_keyword("WITH ROLLUP");
32037        } else {
32038            self.write_keyword("ROLLUP");
32039            self.write("(");
32040            for (i, expr) in e.expressions.iter().enumerate() {
32041                if i > 0 {
32042                    self.write(", ");
32043                }
32044                self.generate_expression(expr)?;
32045            }
32046            self.write(")");
32047        }
32048        Ok(())
32049    }
32050
32051    fn generate_row_format_delimited_property(
32052        &mut self,
32053        e: &RowFormatDelimitedProperty,
32054    ) -> Result<()> {
32055        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
32056        self.write_keyword("ROW FORMAT DELIMITED");
32057        if let Some(fields) = &e.fields {
32058            self.write_space();
32059            self.write_keyword("FIELDS TERMINATED BY");
32060            self.write_space();
32061            self.generate_expression(fields)?;
32062        }
32063        if let Some(escaped) = &e.escaped {
32064            self.write_space();
32065            self.write_keyword("ESCAPED BY");
32066            self.write_space();
32067            self.generate_expression(escaped)?;
32068        }
32069        if let Some(items) = &e.collection_items {
32070            self.write_space();
32071            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
32072            self.write_space();
32073            self.generate_expression(items)?;
32074        }
32075        if let Some(keys) = &e.map_keys {
32076            self.write_space();
32077            self.write_keyword("MAP KEYS TERMINATED BY");
32078            self.write_space();
32079            self.generate_expression(keys)?;
32080        }
32081        if let Some(lines) = &e.lines {
32082            self.write_space();
32083            self.write_keyword("LINES TERMINATED BY");
32084            self.write_space();
32085            self.generate_expression(lines)?;
32086        }
32087        if let Some(null) = &e.null {
32088            self.write_space();
32089            self.write_keyword("NULL DEFINED AS");
32090            self.write_space();
32091            self.generate_expression(null)?;
32092        }
32093        if let Some(serde) = &e.serde {
32094            self.write_space();
32095            self.generate_expression(serde)?;
32096        }
32097        Ok(())
32098    }
32099
32100    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
32101        // ROW FORMAT this
32102        self.write_keyword("ROW FORMAT");
32103        self.write_space();
32104        self.generate_expression(&e.this)?;
32105        Ok(())
32106    }
32107
32108    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
32109        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
32110        self.write_keyword("ROW FORMAT SERDE");
32111        self.write_space();
32112        self.generate_expression(&e.this)?;
32113        if let Some(props) = &e.serde_properties {
32114            self.write_space();
32115            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
32116            self.generate_expression(props)?;
32117        }
32118        Ok(())
32119    }
32120
32121    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
32122        // SHA2(this, length)
32123        self.write_keyword("SHA2");
32124        self.write("(");
32125        self.generate_expression(&e.this)?;
32126        if let Some(length) = e.length {
32127            self.write(", ");
32128            self.write(&length.to_string());
32129        }
32130        self.write(")");
32131        Ok(())
32132    }
32133
32134    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
32135        // SHA2_DIGEST(this, length)
32136        self.write_keyword("SHA2_DIGEST");
32137        self.write("(");
32138        self.generate_expression(&e.this)?;
32139        if let Some(length) = e.length {
32140            self.write(", ");
32141            self.write(&length.to_string());
32142        }
32143        self.write(")");
32144        Ok(())
32145    }
32146
32147    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
32148        let name = if matches!(
32149            self.config.dialect,
32150            Some(crate::dialects::DialectType::Spark)
32151                | Some(crate::dialects::DialectType::Databricks)
32152        ) {
32153            "TRY_ADD"
32154        } else {
32155            "SAFE_ADD"
32156        };
32157        self.write_keyword(name);
32158        self.write("(");
32159        self.generate_expression(&e.this)?;
32160        self.write(", ");
32161        self.generate_expression(&e.expression)?;
32162        self.write(")");
32163        Ok(())
32164    }
32165
32166    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
32167        // SAFE_DIVIDE(this, expression)
32168        self.write_keyword("SAFE_DIVIDE");
32169        self.write("(");
32170        self.generate_expression(&e.this)?;
32171        self.write(", ");
32172        self.generate_expression(&e.expression)?;
32173        self.write(")");
32174        Ok(())
32175    }
32176
32177    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
32178        let name = if matches!(
32179            self.config.dialect,
32180            Some(crate::dialects::DialectType::Spark)
32181                | Some(crate::dialects::DialectType::Databricks)
32182        ) {
32183            "TRY_MULTIPLY"
32184        } else {
32185            "SAFE_MULTIPLY"
32186        };
32187        self.write_keyword(name);
32188        self.write("(");
32189        self.generate_expression(&e.this)?;
32190        self.write(", ");
32191        self.generate_expression(&e.expression)?;
32192        self.write(")");
32193        Ok(())
32194    }
32195
32196    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
32197        let name = if matches!(
32198            self.config.dialect,
32199            Some(crate::dialects::DialectType::Spark)
32200                | Some(crate::dialects::DialectType::Databricks)
32201        ) {
32202            "TRY_SUBTRACT"
32203        } else {
32204            "SAFE_SUBTRACT"
32205        };
32206        self.write_keyword(name);
32207        self.write("(");
32208        self.generate_expression(&e.this)?;
32209        self.write(", ");
32210        self.generate_expression(&e.expression)?;
32211        self.write(")");
32212        Ok(())
32213    }
32214
32215    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32216    /// METHOD (size UNIT) [REPEATABLE (seed)]
32217    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32218        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32219        if matches!(sample.method, SampleMethod::Bucket) {
32220            self.write(" (");
32221            self.write_keyword("BUCKET");
32222            self.write_space();
32223            if let Some(ref num) = sample.bucket_numerator {
32224                self.generate_expression(num)?;
32225            }
32226            self.write_space();
32227            self.write_keyword("OUT OF");
32228            self.write_space();
32229            if let Some(ref denom) = sample.bucket_denominator {
32230                self.generate_expression(denom)?;
32231            }
32232            if let Some(ref field) = sample.bucket_field {
32233                self.write_space();
32234                self.write_keyword("ON");
32235                self.write_space();
32236                self.generate_expression(field)?;
32237            }
32238            self.write(")");
32239            return Ok(());
32240        }
32241
32242        // Output method name if explicitly specified, or for dialects that always require it
32243        let is_snowflake = matches!(
32244            self.config.dialect,
32245            Some(crate::dialects::DialectType::Snowflake)
32246        );
32247        let is_postgres = matches!(
32248            self.config.dialect,
32249            Some(crate::dialects::DialectType::PostgreSQL)
32250                | Some(crate::dialects::DialectType::Redshift)
32251        );
32252        // Databricks and Spark don't output method names
32253        let is_databricks = matches!(
32254            self.config.dialect,
32255            Some(crate::dialects::DialectType::Databricks)
32256        );
32257        let is_spark = matches!(
32258            self.config.dialect,
32259            Some(crate::dialects::DialectType::Spark)
32260        );
32261        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32262        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32263        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32264        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32265            self.write_space();
32266            if !sample.explicit_method && (is_snowflake || force_method) {
32267                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32268                self.write_keyword("BERNOULLI");
32269            } else {
32270                match sample.method {
32271                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32272                    SampleMethod::System => self.write_keyword("SYSTEM"),
32273                    SampleMethod::Block => self.write_keyword("BLOCK"),
32274                    SampleMethod::Row => self.write_keyword("ROW"),
32275                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32276                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32277                    SampleMethod::Bucket => {} // handled above
32278                }
32279            }
32280        }
32281
32282        // Output size, with or without parentheses depending on dialect
32283        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32284        if emit_size_no_parens {
32285            self.write_space();
32286            match &sample.size {
32287                Expression::Tuple(tuple) => {
32288                    for (i, expr) in tuple.expressions.iter().enumerate() {
32289                        if i > 0 {
32290                            self.write(", ");
32291                        }
32292                        self.generate_expression(expr)?;
32293                    }
32294                }
32295                expr => self.generate_expression(expr)?,
32296            }
32297        } else {
32298            self.write(" (");
32299            self.generate_expression(&sample.size)?;
32300        }
32301
32302        // Determine unit
32303        let is_rows_method = matches!(
32304            sample.method,
32305            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32306        );
32307        let is_percent = matches!(
32308            sample.method,
32309            SampleMethod::Percent
32310                | SampleMethod::System
32311                | SampleMethod::Bernoulli
32312                | SampleMethod::Block
32313        );
32314
32315        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32316        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32317        // For Databricks and Spark, always output PERCENT for percentage samples.
32318        let is_presto = matches!(
32319            self.config.dialect,
32320            Some(crate::dialects::DialectType::Presto)
32321                | Some(crate::dialects::DialectType::Trino)
32322                | Some(crate::dialects::DialectType::Athena)
32323        );
32324        let should_output_unit = if is_databricks || is_spark {
32325            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32326            is_percent || is_rows_method || sample.unit_after_size
32327        } else if is_snowflake || is_postgres || is_presto {
32328            sample.unit_after_size
32329        } else {
32330            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32331        };
32332
32333        if should_output_unit {
32334            self.write_space();
32335            if sample.is_percent {
32336                self.write_keyword("PERCENT");
32337            } else if is_rows_method && !sample.unit_after_size {
32338                self.write_keyword("ROWS");
32339            } else if sample.unit_after_size {
32340                match sample.method {
32341                    SampleMethod::Percent
32342                    | SampleMethod::System
32343                    | SampleMethod::Bernoulli
32344                    | SampleMethod::Block => {
32345                        self.write_keyword("PERCENT");
32346                    }
32347                    SampleMethod::Row | SampleMethod::Reservoir => {
32348                        self.write_keyword("ROWS");
32349                    }
32350                    _ => self.write_keyword("ROWS"),
32351                }
32352            } else {
32353                self.write_keyword("PERCENT");
32354            }
32355        }
32356
32357        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32358            if let Some(ref offset) = sample.offset {
32359                self.write_space();
32360                self.write_keyword("OFFSET");
32361                self.write_space();
32362                self.generate_expression(offset)?;
32363            }
32364        }
32365        if !emit_size_no_parens {
32366            self.write(")");
32367        }
32368
32369        Ok(())
32370    }
32371
32372    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32373        // SAMPLE this (ClickHouse uses SAMPLE BY)
32374        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32375            self.write_keyword("SAMPLE BY");
32376        } else {
32377            self.write_keyword("SAMPLE");
32378        }
32379        self.write_space();
32380        self.generate_expression(&e.this)?;
32381        Ok(())
32382    }
32383
32384    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32385        // this (expressions...)
32386        if let Some(this) = &e.this {
32387            self.generate_expression(this)?;
32388        }
32389        if !e.expressions.is_empty() {
32390            // Add space before column list if there's a preceding expression
32391            if e.this.is_some() {
32392                self.write_space();
32393            }
32394            self.write("(");
32395            for (i, expr) in e.expressions.iter().enumerate() {
32396                if i > 0 {
32397                    self.write(", ");
32398                }
32399                self.generate_expression(expr)?;
32400            }
32401            self.write(")");
32402        }
32403        Ok(())
32404    }
32405
32406    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32407        // COMMENT this
32408        self.write_keyword("COMMENT");
32409        self.write_space();
32410        self.generate_expression(&e.this)?;
32411        Ok(())
32412    }
32413
32414    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32415        // [this::]expression
32416        if let Some(this) = &e.this {
32417            self.generate_expression(this)?;
32418            self.write("::");
32419        }
32420        self.generate_expression(&e.expression)?;
32421        Ok(())
32422    }
32423
32424    fn generate_search(&mut self, e: &Search) -> Result<()> {
32425        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32426        self.write_keyword("SEARCH");
32427        self.write("(");
32428        self.generate_expression(&e.this)?;
32429        self.write(", ");
32430        self.generate_expression(&e.expression)?;
32431        if let Some(json_scope) = &e.json_scope {
32432            self.write(", ");
32433            self.generate_expression(json_scope)?;
32434        }
32435        if let Some(analyzer) = &e.analyzer {
32436            self.write(", ");
32437            self.generate_expression(analyzer)?;
32438        }
32439        if let Some(analyzer_options) = &e.analyzer_options {
32440            self.write(", ");
32441            self.generate_expression(analyzer_options)?;
32442        }
32443        if let Some(search_mode) = &e.search_mode {
32444            self.write(", ");
32445            self.generate_expression(search_mode)?;
32446        }
32447        self.write(")");
32448        Ok(())
32449    }
32450
32451    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32452        // SEARCH_IP(this, expression)
32453        self.write_keyword("SEARCH_IP");
32454        self.write("(");
32455        self.generate_expression(&e.this)?;
32456        self.write(", ");
32457        self.generate_expression(&e.expression)?;
32458        self.write(")");
32459        Ok(())
32460    }
32461
32462    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32463        // SECURITY this
32464        self.write_keyword("SECURITY");
32465        self.write_space();
32466        self.generate_expression(&e.this)?;
32467        Ok(())
32468    }
32469
32470    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32471        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32472        self.write("SEMANTIC_VIEW(");
32473
32474        if self.config.pretty {
32475            // Pretty print: each clause on its own line
32476            self.write_newline();
32477            self.indent_level += 1;
32478            self.write_indent();
32479            self.generate_expression(&e.this)?;
32480
32481            if let Some(metrics) = &e.metrics {
32482                self.write_newline();
32483                self.write_indent();
32484                self.write_keyword("METRICS");
32485                self.write_space();
32486                self.generate_semantic_view_tuple(metrics)?;
32487            }
32488            if let Some(dimensions) = &e.dimensions {
32489                self.write_newline();
32490                self.write_indent();
32491                self.write_keyword("DIMENSIONS");
32492                self.write_space();
32493                self.generate_semantic_view_tuple(dimensions)?;
32494            }
32495            if let Some(facts) = &e.facts {
32496                self.write_newline();
32497                self.write_indent();
32498                self.write_keyword("FACTS");
32499                self.write_space();
32500                self.generate_semantic_view_tuple(facts)?;
32501            }
32502            if let Some(where_) = &e.where_ {
32503                self.write_newline();
32504                self.write_indent();
32505                self.write_keyword("WHERE");
32506                self.write_space();
32507                self.generate_expression(where_)?;
32508            }
32509            self.write_newline();
32510            self.indent_level -= 1;
32511            self.write_indent();
32512        } else {
32513            // Compact: all on one line
32514            self.generate_expression(&e.this)?;
32515            if let Some(metrics) = &e.metrics {
32516                self.write_space();
32517                self.write_keyword("METRICS");
32518                self.write_space();
32519                self.generate_semantic_view_tuple(metrics)?;
32520            }
32521            if let Some(dimensions) = &e.dimensions {
32522                self.write_space();
32523                self.write_keyword("DIMENSIONS");
32524                self.write_space();
32525                self.generate_semantic_view_tuple(dimensions)?;
32526            }
32527            if let Some(facts) = &e.facts {
32528                self.write_space();
32529                self.write_keyword("FACTS");
32530                self.write_space();
32531                self.generate_semantic_view_tuple(facts)?;
32532            }
32533            if let Some(where_) = &e.where_ {
32534                self.write_space();
32535                self.write_keyword("WHERE");
32536                self.write_space();
32537                self.generate_expression(where_)?;
32538            }
32539        }
32540        self.write(")");
32541        Ok(())
32542    }
32543
32544    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32545    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32546        if let Expression::Tuple(t) = expr {
32547            for (i, e) in t.expressions.iter().enumerate() {
32548                if i > 0 {
32549                    self.write(", ");
32550                }
32551                self.generate_expression(e)?;
32552            }
32553        } else {
32554            self.generate_expression(expr)?;
32555        }
32556        Ok(())
32557    }
32558
32559    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32560        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32561        if let Some(start) = &e.start {
32562            self.write_keyword("START WITH");
32563            self.write_space();
32564            self.generate_expression(start)?;
32565        }
32566        if let Some(increment) = &e.increment {
32567            self.write_space();
32568            self.write_keyword("INCREMENT BY");
32569            self.write_space();
32570            self.generate_expression(increment)?;
32571        }
32572        if let Some(minvalue) = &e.minvalue {
32573            self.write_space();
32574            self.write_keyword("MINVALUE");
32575            self.write_space();
32576            self.generate_expression(minvalue)?;
32577        }
32578        if let Some(maxvalue) = &e.maxvalue {
32579            self.write_space();
32580            self.write_keyword("MAXVALUE");
32581            self.write_space();
32582            self.generate_expression(maxvalue)?;
32583        }
32584        if let Some(cache) = &e.cache {
32585            self.write_space();
32586            self.write_keyword("CACHE");
32587            self.write_space();
32588            self.generate_expression(cache)?;
32589        }
32590        if let Some(owned) = &e.owned {
32591            self.write_space();
32592            self.write_keyword("OWNED BY");
32593            self.write_space();
32594            self.generate_expression(owned)?;
32595        }
32596        for opt in &e.options {
32597            self.write_space();
32598            self.generate_expression(opt)?;
32599        }
32600        Ok(())
32601    }
32602
32603    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32604        // [WITH] SERDEPROPERTIES (expressions)
32605        if e.with_.is_some() {
32606            self.write_keyword("WITH");
32607            self.write_space();
32608        }
32609        self.write_keyword("SERDEPROPERTIES");
32610        self.write(" (");
32611        for (i, expr) in e.expressions.iter().enumerate() {
32612            if i > 0 {
32613                self.write(", ");
32614            }
32615            // Generate key=value without spaces around =
32616            match expr {
32617                Expression::Eq(eq) => {
32618                    self.generate_expression(&eq.left)?;
32619                    self.write("=");
32620                    self.generate_expression(&eq.right)?;
32621                }
32622                _ => self.generate_expression(expr)?,
32623            }
32624        }
32625        self.write(")");
32626        Ok(())
32627    }
32628
32629    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32630        // @@[kind.]this
32631        self.write("@@");
32632        if let Some(kind) = &e.kind {
32633            self.write(kind);
32634            self.write(".");
32635        }
32636        self.generate_expression(&e.this)?;
32637        Ok(())
32638    }
32639
32640    fn generate_set(&mut self, e: &Set) -> Result<()> {
32641        // SET/UNSET [TAG] expressions
32642        if e.unset.is_some() {
32643            self.write_keyword("UNSET");
32644        } else {
32645            self.write_keyword("SET");
32646        }
32647        if e.tag.is_some() {
32648            self.write_space();
32649            self.write_keyword("TAG");
32650        }
32651        if !e.expressions.is_empty() {
32652            self.write_space();
32653            for (i, expr) in e.expressions.iter().enumerate() {
32654                if i > 0 {
32655                    self.write(", ");
32656                }
32657                self.generate_expression(expr)?;
32658            }
32659        }
32660        Ok(())
32661    }
32662
32663    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32664        // SET this or SETCONFIG this
32665        self.write_keyword("SET");
32666        self.write_space();
32667        self.generate_expression(&e.this)?;
32668        Ok(())
32669    }
32670
32671    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32672        // [kind] name = value
32673        if let Some(kind) = &e.kind {
32674            self.write_keyword(kind);
32675            self.write_space();
32676        }
32677        self.generate_expression(&e.name)?;
32678        self.write(" = ");
32679        self.generate_expression(&e.value)?;
32680        Ok(())
32681    }
32682
32683    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32684        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32685        if let Some(with_) = &e.with_ {
32686            self.generate_expression(with_)?;
32687            self.write_space();
32688        }
32689        self.generate_expression(&e.this)?;
32690        self.write_space();
32691        // kind should be UNION, INTERSECT, EXCEPT, etc.
32692        if let Some(kind) = &e.kind {
32693            self.write_keyword(kind);
32694        }
32695        if e.distinct {
32696            self.write_space();
32697            self.write_keyword("DISTINCT");
32698        } else {
32699            self.write_space();
32700            self.write_keyword("ALL");
32701        }
32702        if e.by_name.is_some() {
32703            self.write_space();
32704            self.write_keyword("BY NAME");
32705        }
32706        self.write_space();
32707        self.generate_expression(&e.expression)?;
32708        Ok(())
32709    }
32710
32711    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32712        // SET or MULTISET
32713        if e.multi.is_some() {
32714            self.write_keyword("MULTISET");
32715        } else {
32716            self.write_keyword("SET");
32717        }
32718        Ok(())
32719    }
32720
32721    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32722        // SETTINGS expressions
32723        self.write_keyword("SETTINGS");
32724        if self.config.pretty && e.expressions.len() > 1 {
32725            // Pretty print: each setting on its own line, indented
32726            self.indent_level += 1;
32727            for (i, expr) in e.expressions.iter().enumerate() {
32728                if i > 0 {
32729                    self.write(",");
32730                }
32731                self.write_newline();
32732                self.write_indent();
32733                self.generate_expression(expr)?;
32734            }
32735            self.indent_level -= 1;
32736        } else {
32737            self.write_space();
32738            for (i, expr) in e.expressions.iter().enumerate() {
32739                if i > 0 {
32740                    self.write(", ");
32741                }
32742                self.generate_expression(expr)?;
32743            }
32744        }
32745        Ok(())
32746    }
32747
32748    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32749        // SHARING = this
32750        self.write_keyword("SHARING");
32751        if let Some(this) = &e.this {
32752            self.write(" = ");
32753            self.generate_expression(this)?;
32754        }
32755        Ok(())
32756    }
32757
32758    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32759        // Python array slicing: begin:end:step
32760        if let Some(begin) = &e.this {
32761            self.generate_expression(begin)?;
32762        }
32763        self.write(":");
32764        if let Some(end) = &e.expression {
32765            self.generate_expression(end)?;
32766        }
32767        if let Some(step) = &e.step {
32768            self.write(":");
32769            self.generate_expression(step)?;
32770        }
32771        Ok(())
32772    }
32773
32774    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32775        // SORT_ARRAY(this, asc)
32776        self.write_keyword("SORT_ARRAY");
32777        self.write("(");
32778        self.generate_expression(&e.this)?;
32779        if let Some(asc) = &e.asc {
32780            self.write(", ");
32781            self.generate_expression(asc)?;
32782        }
32783        self.write(")");
32784        Ok(())
32785    }
32786
32787    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32788        // SORT BY expressions
32789        self.write_keyword("SORT BY");
32790        self.write_space();
32791        for (i, expr) in e.expressions.iter().enumerate() {
32792            if i > 0 {
32793                self.write(", ");
32794            }
32795            self.generate_ordered(expr)?;
32796        }
32797        Ok(())
32798    }
32799
32800    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32801        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32802        if e.compound.is_some() {
32803            self.write_keyword("COMPOUND");
32804            self.write_space();
32805        }
32806        self.write_keyword("SORTKEY");
32807        self.write("(");
32808        // If this is a Tuple, unwrap its contents to avoid double parentheses
32809        if let Expression::Tuple(t) = e.this.as_ref() {
32810            for (i, expr) in t.expressions.iter().enumerate() {
32811                if i > 0 {
32812                    self.write(", ");
32813                }
32814                self.generate_expression(expr)?;
32815            }
32816        } else {
32817            self.generate_expression(&e.this)?;
32818        }
32819        self.write(")");
32820        Ok(())
32821    }
32822
32823    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32824        // SPLIT_PART(this, delimiter, part_index)
32825        self.write_keyword("SPLIT_PART");
32826        self.write("(");
32827        self.generate_expression(&e.this)?;
32828        if let Some(delimiter) = &e.delimiter {
32829            self.write(", ");
32830            self.generate_expression(delimiter)?;
32831        }
32832        if let Some(part_index) = &e.part_index {
32833            self.write(", ");
32834            self.generate_expression(part_index)?;
32835        }
32836        self.write(")");
32837        Ok(())
32838    }
32839
32840    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32841        // READS SQL DATA or MODIFIES SQL DATA, etc.
32842        self.generate_expression(&e.this)?;
32843        Ok(())
32844    }
32845
32846    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32847        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32848        self.write_keyword("SQL SECURITY");
32849        self.write_space();
32850        self.generate_expression(&e.this)?;
32851        Ok(())
32852    }
32853
32854    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32855        // ST_DISTANCE(this, expression, [use_spheroid])
32856        self.write_keyword("ST_DISTANCE");
32857        self.write("(");
32858        self.generate_expression(&e.this)?;
32859        self.write(", ");
32860        self.generate_expression(&e.expression)?;
32861        if let Some(use_spheroid) = &e.use_spheroid {
32862            self.write(", ");
32863            self.generate_expression(use_spheroid)?;
32864        }
32865        self.write(")");
32866        Ok(())
32867    }
32868
32869    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32870        // ST_POINT(this, expression)
32871        self.write_keyword("ST_POINT");
32872        self.write("(");
32873        self.generate_expression(&e.this)?;
32874        self.write(", ");
32875        self.generate_expression(&e.expression)?;
32876        self.write(")");
32877        Ok(())
32878    }
32879
32880    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32881        // IMMUTABLE, STABLE, VOLATILE
32882        self.generate_expression(&e.this)?;
32883        Ok(())
32884    }
32885
32886    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32887        // STANDARD_HASH(this, [expression])
32888        self.write_keyword("STANDARD_HASH");
32889        self.write("(");
32890        self.generate_expression(&e.this)?;
32891        if let Some(expression) = &e.expression {
32892            self.write(", ");
32893            self.generate_expression(expression)?;
32894        }
32895        self.write(")");
32896        Ok(())
32897    }
32898
32899    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32900        // STORED BY this
32901        self.write_keyword("STORED BY");
32902        self.write_space();
32903        self.generate_expression(&e.this)?;
32904        Ok(())
32905    }
32906
32907    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32908        // STRPOS(this, substr) or STRPOS(this, substr, position)
32909        // Different dialects have different function names
32910        use crate::dialects::DialectType;
32911        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32912            // Snowflake: CHARINDEX(substr, str[, position])
32913            self.write_keyword("CHARINDEX");
32914            self.write("(");
32915            if let Some(substr) = &e.substr {
32916                self.generate_expression(substr)?;
32917                self.write(", ");
32918            }
32919            self.generate_expression(&e.this)?;
32920            if let Some(position) = &e.position {
32921                self.write(", ");
32922                self.generate_expression(position)?;
32923            }
32924            self.write(")");
32925        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32926            self.write_keyword("POSITION");
32927            self.write("(");
32928            self.generate_expression(&e.this)?;
32929            if let Some(substr) = &e.substr {
32930                self.write(", ");
32931                self.generate_expression(substr)?;
32932            }
32933            if let Some(position) = &e.position {
32934                self.write(", ");
32935                self.generate_expression(position)?;
32936            }
32937            if let Some(occurrence) = &e.occurrence {
32938                self.write(", ");
32939                self.generate_expression(occurrence)?;
32940            }
32941            self.write(")");
32942        } else if matches!(
32943            self.config.dialect,
32944            Some(DialectType::SQLite)
32945                | Some(DialectType::Oracle)
32946                | Some(DialectType::BigQuery)
32947                | Some(DialectType::Teradata)
32948        ) {
32949            self.write_keyword("INSTR");
32950            self.write("(");
32951            self.generate_expression(&e.this)?;
32952            if let Some(substr) = &e.substr {
32953                self.write(", ");
32954                self.generate_expression(substr)?;
32955            }
32956            if let Some(position) = &e.position {
32957                self.write(", ");
32958                self.generate_expression(position)?;
32959            } else if e.occurrence.is_some() {
32960                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32961                // Default start position is 1
32962                self.write(", 1");
32963            }
32964            if let Some(occurrence) = &e.occurrence {
32965                self.write(", ");
32966                self.generate_expression(occurrence)?;
32967            }
32968            self.write(")");
32969        } else if matches!(
32970            self.config.dialect,
32971            Some(DialectType::MySQL)
32972                | Some(DialectType::SingleStore)
32973                | Some(DialectType::Doris)
32974                | Some(DialectType::StarRocks)
32975                | Some(DialectType::Hive)
32976                | Some(DialectType::Spark)
32977                | Some(DialectType::Databricks)
32978        ) {
32979            // LOCATE(substr, str[, position]) - substr first
32980            self.write_keyword("LOCATE");
32981            self.write("(");
32982            if let Some(substr) = &e.substr {
32983                self.generate_expression(substr)?;
32984                self.write(", ");
32985            }
32986            self.generate_expression(&e.this)?;
32987            if let Some(position) = &e.position {
32988                self.write(", ");
32989                self.generate_expression(position)?;
32990            }
32991            self.write(")");
32992        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32993            // CHARINDEX(substr, str[, position])
32994            self.write_keyword("CHARINDEX");
32995            self.write("(");
32996            if let Some(substr) = &e.substr {
32997                self.generate_expression(substr)?;
32998                self.write(", ");
32999            }
33000            self.generate_expression(&e.this)?;
33001            if let Some(position) = &e.position {
33002                self.write(", ");
33003                self.generate_expression(position)?;
33004            }
33005            self.write(")");
33006        } else if matches!(
33007            self.config.dialect,
33008            Some(DialectType::PostgreSQL)
33009                | Some(DialectType::Materialize)
33010                | Some(DialectType::RisingWave)
33011                | Some(DialectType::Redshift)
33012        ) {
33013            // POSITION(substr IN str) syntax
33014            self.write_keyword("POSITION");
33015            self.write("(");
33016            if let Some(substr) = &e.substr {
33017                self.generate_expression(substr)?;
33018                self.write(" IN ");
33019            }
33020            self.generate_expression(&e.this)?;
33021            self.write(")");
33022        } else {
33023            self.write_keyword("STRPOS");
33024            self.write("(");
33025            self.generate_expression(&e.this)?;
33026            if let Some(substr) = &e.substr {
33027                self.write(", ");
33028                self.generate_expression(substr)?;
33029            }
33030            if let Some(position) = &e.position {
33031                self.write(", ");
33032                self.generate_expression(position)?;
33033            }
33034            if let Some(occurrence) = &e.occurrence {
33035                self.write(", ");
33036                self.generate_expression(occurrence)?;
33037            }
33038            self.write(")");
33039        }
33040        Ok(())
33041    }
33042
33043    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
33044        match self.config.dialect {
33045            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
33046                // TO_DATE(this, java_format)
33047                self.write_keyword("TO_DATE");
33048                self.write("(");
33049                self.generate_expression(&e.this)?;
33050                if let Some(format) = &e.format {
33051                    self.write(", '");
33052                    self.write(&Self::strftime_to_java_format(format));
33053                    self.write("'");
33054                }
33055                self.write(")");
33056            }
33057            Some(DialectType::DuckDB) => {
33058                // CAST(STRPTIME(this, format) AS DATE)
33059                self.write_keyword("CAST");
33060                self.write("(");
33061                self.write_keyword("STRPTIME");
33062                self.write("(");
33063                self.generate_expression(&e.this)?;
33064                if let Some(format) = &e.format {
33065                    self.write(", '");
33066                    self.write(format);
33067                    self.write("'");
33068                }
33069                self.write(")");
33070                self.write_keyword(" AS ");
33071                self.write_keyword("DATE");
33072                self.write(")");
33073            }
33074            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
33075                // TO_DATE(this, pg_format)
33076                self.write_keyword("TO_DATE");
33077                self.write("(");
33078                self.generate_expression(&e.this)?;
33079                if let Some(format) = &e.format {
33080                    self.write(", '");
33081                    self.write(&Self::strftime_to_postgres_format(format));
33082                    self.write("'");
33083                }
33084                self.write(")");
33085            }
33086            Some(DialectType::BigQuery) => {
33087                // PARSE_DATE(format, this) - note: format comes first for BigQuery
33088                self.write_keyword("PARSE_DATE");
33089                self.write("(");
33090                if let Some(format) = &e.format {
33091                    self.write("'");
33092                    self.write(format);
33093                    self.write("'");
33094                    self.write(", ");
33095                }
33096                self.generate_expression(&e.this)?;
33097                self.write(")");
33098            }
33099            Some(DialectType::Teradata) => {
33100                // CAST(this AS DATE FORMAT 'teradata_fmt')
33101                self.write_keyword("CAST");
33102                self.write("(");
33103                self.generate_expression(&e.this)?;
33104                self.write_keyword(" AS ");
33105                self.write_keyword("DATE");
33106                if let Some(format) = &e.format {
33107                    self.write_keyword(" FORMAT ");
33108                    self.write("'");
33109                    self.write(&Self::strftime_to_teradata_format(format));
33110                    self.write("'");
33111                }
33112                self.write(")");
33113            }
33114            _ => {
33115                // STR_TO_DATE(this, format) - MySQL default
33116                self.write_keyword("STR_TO_DATE");
33117                self.write("(");
33118                self.generate_expression(&e.this)?;
33119                if let Some(format) = &e.format {
33120                    self.write(", '");
33121                    self.write(format);
33122                    self.write("'");
33123                }
33124                self.write(")");
33125            }
33126        }
33127        Ok(())
33128    }
33129
33130    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
33131    fn strftime_to_teradata_format(fmt: &str) -> String {
33132        let mut result = fmt.to_string();
33133        result = result.replace("%Y", "YYYY");
33134        result = result.replace("%y", "YY");
33135        result = result.replace("%m", "MM");
33136        result = result.replace("%B", "MMMM");
33137        result = result.replace("%b", "MMM");
33138        result = result.replace("%d", "DD");
33139        result = result.replace("%j", "DDD");
33140        result = result.replace("%H", "HH");
33141        result = result.replace("%M", "MI");
33142        result = result.replace("%S", "SS");
33143        result = result.replace("%f", "SSSSSS");
33144        result = result.replace("%A", "EEEE");
33145        result = result.replace("%a", "EEE");
33146        result
33147    }
33148
33149    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33150    /// Public static version for use by other modules
33151    pub fn strftime_to_java_format_static(fmt: &str) -> String {
33152        Self::strftime_to_java_format(fmt)
33153    }
33154
33155    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
33156    fn strftime_to_java_format(fmt: &str) -> String {
33157        let mut result = fmt.to_string();
33158        // Handle non-padded variants BEFORE their padded counterparts
33159        result = result.replace("%-d", "d");
33160        result = result.replace("%-m", "M");
33161        result = result.replace("%-H", "H");
33162        result = result.replace("%-M", "m");
33163        result = result.replace("%-S", "s");
33164        result = result.replace("%Y", "yyyy");
33165        result = result.replace("%y", "yy");
33166        result = result.replace("%m", "MM");
33167        result = result.replace("%B", "MMMM");
33168        result = result.replace("%b", "MMM");
33169        result = result.replace("%d", "dd");
33170        result = result.replace("%j", "DDD");
33171        result = result.replace("%H", "HH");
33172        result = result.replace("%M", "mm");
33173        result = result.replace("%S", "ss");
33174        result = result.replace("%f", "SSSSSS");
33175        result = result.replace("%A", "EEEE");
33176        result = result.replace("%a", "EEE");
33177        result
33178    }
33179
33180    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
33181    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
33182    fn strftime_to_tsql_format(fmt: &str) -> String {
33183        let mut result = fmt.to_string();
33184        // Handle non-padded variants BEFORE their padded counterparts
33185        result = result.replace("%-d", "d");
33186        result = result.replace("%-m", "M");
33187        result = result.replace("%-H", "H");
33188        result = result.replace("%-M", "m");
33189        result = result.replace("%-S", "s");
33190        result = result.replace("%Y", "yyyy");
33191        result = result.replace("%y", "yy");
33192        result = result.replace("%m", "MM");
33193        result = result.replace("%B", "MMMM");
33194        result = result.replace("%b", "MMM");
33195        result = result.replace("%d", "dd");
33196        result = result.replace("%j", "DDD");
33197        result = result.replace("%H", "HH");
33198        result = result.replace("%M", "mm");
33199        result = result.replace("%S", "ss");
33200        result = result.replace("%f", "ffffff");
33201        result = result.replace("%A", "dddd");
33202        result = result.replace("%a", "ddd");
33203        result
33204    }
33205
33206    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33207    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33208    fn decompose_json_path(path: &str) -> Vec<String> {
33209        let mut parts = Vec::new();
33210        // Strip leading $ and optional .
33211        let path = if path.starts_with("$.") {
33212            &path[2..]
33213        } else if path.starts_with('$') {
33214            &path[1..]
33215        } else {
33216            path
33217        };
33218        if path.is_empty() {
33219            return parts;
33220        }
33221        let mut current = String::new();
33222        let chars: Vec<char> = path.chars().collect();
33223        let mut i = 0;
33224        while i < chars.len() {
33225            match chars[i] {
33226                '.' => {
33227                    if !current.is_empty() {
33228                        parts.push(current.clone());
33229                        current.clear();
33230                    }
33231                    i += 1;
33232                }
33233                '[' => {
33234                    if !current.is_empty() {
33235                        parts.push(current.clone());
33236                        current.clear();
33237                    }
33238                    i += 1;
33239                    // Read the content inside brackets
33240                    let mut bracket_content = String::new();
33241                    while i < chars.len() && chars[i] != ']' {
33242                        // Skip quotes inside brackets
33243                        if chars[i] == '"' || chars[i] == '\'' {
33244                            let quote = chars[i];
33245                            i += 1;
33246                            while i < chars.len() && chars[i] != quote {
33247                                bracket_content.push(chars[i]);
33248                                i += 1;
33249                            }
33250                            if i < chars.len() {
33251                                i += 1;
33252                            } // skip closing quote
33253                        } else {
33254                            bracket_content.push(chars[i]);
33255                            i += 1;
33256                        }
33257                    }
33258                    if i < chars.len() {
33259                        i += 1;
33260                    } // skip ]
33261                      // Skip wildcard [*] - don't add as a part
33262                    if bracket_content != "*" {
33263                        parts.push(bracket_content);
33264                    }
33265                }
33266                _ => {
33267                    current.push(chars[i]);
33268                    i += 1;
33269                }
33270            }
33271        }
33272        if !current.is_empty() {
33273            parts.push(current);
33274        }
33275        parts
33276    }
33277
33278    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
33279    fn strftime_to_postgres_format(fmt: &str) -> String {
33280        let mut result = fmt.to_string();
33281        // Handle non-padded variants BEFORE their padded counterparts
33282        result = result.replace("%-d", "FMDD");
33283        result = result.replace("%-m", "FMMM");
33284        result = result.replace("%-H", "FMHH24");
33285        result = result.replace("%-M", "FMMI");
33286        result = result.replace("%-S", "FMSS");
33287        result = result.replace("%Y", "YYYY");
33288        result = result.replace("%y", "YY");
33289        result = result.replace("%m", "MM");
33290        result = result.replace("%B", "Month");
33291        result = result.replace("%b", "Mon");
33292        result = result.replace("%d", "DD");
33293        result = result.replace("%j", "DDD");
33294        result = result.replace("%H", "HH24");
33295        result = result.replace("%M", "MI");
33296        result = result.replace("%S", "SS");
33297        result = result.replace("%f", "US");
33298        result = result.replace("%A", "Day");
33299        result = result.replace("%a", "Dy");
33300        result
33301    }
33302
33303    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33304    fn strftime_to_snowflake_format(fmt: &str) -> String {
33305        let mut result = fmt.to_string();
33306        // Handle %-d (non-padded day) before %d (padded day)
33307        result = result.replace("%-d", "dd");
33308        result = result.replace("%-m", "mm"); // non-padded month
33309        result = result.replace("%Y", "yyyy");
33310        result = result.replace("%y", "yy");
33311        result = result.replace("%m", "mm");
33312        result = result.replace("%d", "DD");
33313        result = result.replace("%H", "hh24");
33314        result = result.replace("%M", "mi");
33315        result = result.replace("%S", "ss");
33316        result = result.replace("%f", "ff");
33317        result
33318    }
33319
33320    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33321        // STR_TO_MAP(this, pair_delim, key_value_delim)
33322        self.write_keyword("STR_TO_MAP");
33323        self.write("(");
33324        self.generate_expression(&e.this)?;
33325        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33326        let needs_defaults = matches!(
33327            self.config.dialect,
33328            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33329        );
33330        if let Some(pair_delim) = &e.pair_delim {
33331            self.write(", ");
33332            self.generate_expression(pair_delim)?;
33333        } else if needs_defaults {
33334            self.write(", ','");
33335        }
33336        if let Some(key_value_delim) = &e.key_value_delim {
33337            self.write(", ");
33338            self.generate_expression(key_value_delim)?;
33339        } else if needs_defaults {
33340            self.write(", ':'");
33341        }
33342        self.write(")");
33343        Ok(())
33344    }
33345
33346    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33347        // Detect format style: strftime (starts with %) vs Snowflake/Java
33348        let is_strftime = e.format.contains('%');
33349        // Helper: get strftime format from whatever style is stored
33350        let to_strftime = |f: &str| -> String {
33351            if is_strftime {
33352                f.to_string()
33353            } else {
33354                Self::snowflake_format_to_strftime(f)
33355            }
33356        };
33357        // Helper: get Java format
33358        let to_java = |f: &str| -> String {
33359            if is_strftime {
33360                Self::strftime_to_java_format(f)
33361            } else {
33362                Self::snowflake_format_to_spark(f)
33363            }
33364        };
33365        // Helper: get PG format
33366        let to_pg = |f: &str| -> String {
33367            if is_strftime {
33368                Self::strftime_to_postgres_format(f)
33369            } else {
33370                Self::convert_strptime_to_postgres_format(f)
33371            }
33372        };
33373
33374        match self.config.dialect {
33375            Some(DialectType::Exasol) => {
33376                self.write_keyword("TO_DATE");
33377                self.write("(");
33378                self.generate_expression(&e.this)?;
33379                self.write(", '");
33380                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33381                self.write("'");
33382                self.write(")");
33383            }
33384            Some(DialectType::BigQuery) => {
33385                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33386                let fmt = to_strftime(&e.format);
33387                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33388                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33389                self.write_keyword("PARSE_TIMESTAMP");
33390                self.write("('");
33391                self.write(&fmt);
33392                self.write("', ");
33393                self.generate_expression(&e.this)?;
33394                self.write(")");
33395            }
33396            Some(DialectType::Hive) => {
33397                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33398                // Check both the raw format and the converted format (in case it's already Java)
33399                let java_fmt = to_java(&e.format);
33400                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33401                    || java_fmt == "yyyy-MM-dd"
33402                    || e.format == "yyyy-MM-dd HH:mm:ss"
33403                    || e.format == "yyyy-MM-dd"
33404                {
33405                    self.write_keyword("CAST");
33406                    self.write("(");
33407                    self.generate_expression(&e.this)?;
33408                    self.write(" ");
33409                    self.write_keyword("AS TIMESTAMP");
33410                    self.write(")");
33411                } else {
33412                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33413                    self.write_keyword("CAST");
33414                    self.write("(");
33415                    self.write_keyword("FROM_UNIXTIME");
33416                    self.write("(");
33417                    self.write_keyword("UNIX_TIMESTAMP");
33418                    self.write("(");
33419                    self.generate_expression(&e.this)?;
33420                    self.write(", '");
33421                    self.write(&java_fmt);
33422                    self.write("')");
33423                    self.write(") ");
33424                    self.write_keyword("AS TIMESTAMP");
33425                    self.write(")");
33426                }
33427            }
33428            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33429                // Spark: TO_TIMESTAMP(value, java_format)
33430                let java_fmt = to_java(&e.format);
33431                self.write_keyword("TO_TIMESTAMP");
33432                self.write("(");
33433                self.generate_expression(&e.this)?;
33434                self.write(", '");
33435                self.write(&java_fmt);
33436                self.write("')");
33437            }
33438            Some(DialectType::MySQL) => {
33439                // MySQL: STR_TO_DATE(value, format)
33440                let mut fmt = to_strftime(&e.format);
33441                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33442                fmt = fmt.replace("%-d", "%e");
33443                fmt = fmt.replace("%-m", "%c");
33444                fmt = fmt.replace("%H:%M:%S", "%T");
33445                self.write_keyword("STR_TO_DATE");
33446                self.write("(");
33447                self.generate_expression(&e.this)?;
33448                self.write(", '");
33449                self.write(&fmt);
33450                self.write("')");
33451            }
33452            Some(DialectType::Drill) => {
33453                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33454                let java_fmt = to_java(&e.format);
33455                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33456                let java_fmt = java_fmt.replace('T', "''T''");
33457                self.write_keyword("TO_TIMESTAMP");
33458                self.write("(");
33459                self.generate_expression(&e.this)?;
33460                self.write(", '");
33461                self.write(&java_fmt);
33462                self.write("')");
33463            }
33464            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33465                // Presto: DATE_PARSE(value, strftime_format)
33466                let mut fmt = to_strftime(&e.format);
33467                // Presto uses %e for non-padded day, %T for %H:%M:%S
33468                fmt = fmt.replace("%-d", "%e");
33469                fmt = fmt.replace("%-m", "%c");
33470                fmt = fmt.replace("%H:%M:%S", "%T");
33471                self.write_keyword("DATE_PARSE");
33472                self.write("(");
33473                self.generate_expression(&e.this)?;
33474                self.write(", '");
33475                self.write(&fmt);
33476                self.write("')");
33477            }
33478            Some(DialectType::DuckDB) => {
33479                // DuckDB: STRPTIME(value, strftime_format)
33480                let fmt = to_strftime(&e.format);
33481                self.write_keyword("STRPTIME");
33482                self.write("(");
33483                self.generate_expression(&e.this)?;
33484                self.write(", '");
33485                self.write(&fmt);
33486                self.write("')");
33487            }
33488            Some(DialectType::PostgreSQL)
33489            | Some(DialectType::Redshift)
33490            | Some(DialectType::Materialize) => {
33491                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33492                let pg_fmt = to_pg(&e.format);
33493                self.write_keyword("TO_TIMESTAMP");
33494                self.write("(");
33495                self.generate_expression(&e.this)?;
33496                self.write(", '");
33497                self.write(&pg_fmt);
33498                self.write("')");
33499            }
33500            Some(DialectType::Oracle) => {
33501                // Oracle: TO_TIMESTAMP(value, pg_format)
33502                let pg_fmt = to_pg(&e.format);
33503                self.write_keyword("TO_TIMESTAMP");
33504                self.write("(");
33505                self.generate_expression(&e.this)?;
33506                self.write(", '");
33507                self.write(&pg_fmt);
33508                self.write("')");
33509            }
33510            Some(DialectType::Snowflake) => {
33511                // Snowflake: TO_TIMESTAMP(value, format) - native format
33512                self.write_keyword("TO_TIMESTAMP");
33513                self.write("(");
33514                self.generate_expression(&e.this)?;
33515                self.write(", '");
33516                self.write(&e.format);
33517                self.write("')");
33518            }
33519            _ => {
33520                // Default: STR_TO_TIME(this, format)
33521                self.write_keyword("STR_TO_TIME");
33522                self.write("(");
33523                self.generate_expression(&e.this)?;
33524                self.write(", '");
33525                self.write(&e.format);
33526                self.write("'");
33527                self.write(")");
33528            }
33529        }
33530        Ok(())
33531    }
33532
33533    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33534    fn snowflake_format_to_strftime(format: &str) -> String {
33535        let mut result = String::new();
33536        let chars: Vec<char> = format.chars().collect();
33537        let mut i = 0;
33538        while i < chars.len() {
33539            let remaining = &format[i..];
33540            if remaining.starts_with("yyyy") {
33541                result.push_str("%Y");
33542                i += 4;
33543            } else if remaining.starts_with("yy") {
33544                result.push_str("%y");
33545                i += 2;
33546            } else if remaining.starts_with("mmmm") {
33547                result.push_str("%B"); // full month name
33548                i += 4;
33549            } else if remaining.starts_with("mon") {
33550                result.push_str("%b"); // abbreviated month
33551                i += 3;
33552            } else if remaining.starts_with("mm") {
33553                result.push_str("%m");
33554                i += 2;
33555            } else if remaining.starts_with("DD") {
33556                result.push_str("%d");
33557                i += 2;
33558            } else if remaining.starts_with("dy") {
33559                result.push_str("%a"); // abbreviated day name
33560                i += 2;
33561            } else if remaining.starts_with("hh24") {
33562                result.push_str("%H");
33563                i += 4;
33564            } else if remaining.starts_with("hh12") {
33565                result.push_str("%I");
33566                i += 4;
33567            } else if remaining.starts_with("hh") {
33568                result.push_str("%H");
33569                i += 2;
33570            } else if remaining.starts_with("mi") {
33571                result.push_str("%M");
33572                i += 2;
33573            } else if remaining.starts_with("ss") {
33574                result.push_str("%S");
33575                i += 2;
33576            } else if remaining.starts_with("ff") {
33577                // Fractional seconds
33578                result.push_str("%f");
33579                i += 2;
33580                // Skip digits after ff (ff3, ff6, ff9)
33581                while i < chars.len() && chars[i].is_ascii_digit() {
33582                    i += 1;
33583                }
33584            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33585                result.push_str("%p");
33586                i += 2;
33587            } else if remaining.starts_with("tz") {
33588                result.push_str("%Z");
33589                i += 2;
33590            } else {
33591                result.push(chars[i]);
33592                i += 1;
33593            }
33594        }
33595        result
33596    }
33597
33598    /// Convert Snowflake normalized format to Spark format (Java-style)
33599    fn snowflake_format_to_spark(format: &str) -> String {
33600        let mut result = String::new();
33601        let chars: Vec<char> = format.chars().collect();
33602        let mut i = 0;
33603        while i < chars.len() {
33604            let remaining = &format[i..];
33605            if remaining.starts_with("yyyy") {
33606                result.push_str("yyyy");
33607                i += 4;
33608            } else if remaining.starts_with("yy") {
33609                result.push_str("yy");
33610                i += 2;
33611            } else if remaining.starts_with("mmmm") {
33612                result.push_str("MMMM"); // full month name
33613                i += 4;
33614            } else if remaining.starts_with("mon") {
33615                result.push_str("MMM"); // abbreviated month
33616                i += 3;
33617            } else if remaining.starts_with("mm") {
33618                result.push_str("MM");
33619                i += 2;
33620            } else if remaining.starts_with("DD") {
33621                result.push_str("dd");
33622                i += 2;
33623            } else if remaining.starts_with("dy") {
33624                result.push_str("EEE"); // abbreviated day name
33625                i += 2;
33626            } else if remaining.starts_with("hh24") {
33627                result.push_str("HH");
33628                i += 4;
33629            } else if remaining.starts_with("hh12") {
33630                result.push_str("hh");
33631                i += 4;
33632            } else if remaining.starts_with("hh") {
33633                result.push_str("HH");
33634                i += 2;
33635            } else if remaining.starts_with("mi") {
33636                result.push_str("mm");
33637                i += 2;
33638            } else if remaining.starts_with("ss") {
33639                result.push_str("ss");
33640                i += 2;
33641            } else if remaining.starts_with("ff") {
33642                result.push_str("SSS"); // milliseconds
33643                i += 2;
33644                // Skip digits after ff
33645                while i < chars.len() && chars[i].is_ascii_digit() {
33646                    i += 1;
33647                }
33648            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33649                result.push_str("a");
33650                i += 2;
33651            } else if remaining.starts_with("tz") {
33652                result.push_str("z");
33653                i += 2;
33654            } else {
33655                result.push(chars[i]);
33656                i += 1;
33657            }
33658        }
33659        result
33660    }
33661
33662    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33663        match self.config.dialect {
33664            Some(DialectType::DuckDB) => {
33665                // DuckDB: EPOCH(STRPTIME(value, format))
33666                self.write_keyword("EPOCH");
33667                self.write("(");
33668                self.write_keyword("STRPTIME");
33669                self.write("(");
33670                if let Some(this) = &e.this {
33671                    self.generate_expression(this)?;
33672                }
33673                if let Some(format) = &e.format {
33674                    self.write(", '");
33675                    self.write(format);
33676                    self.write("'");
33677                }
33678                self.write("))");
33679            }
33680            Some(DialectType::Hive) => {
33681                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33682                self.write_keyword("UNIX_TIMESTAMP");
33683                self.write("(");
33684                if let Some(this) = &e.this {
33685                    self.generate_expression(this)?;
33686                }
33687                if let Some(format) = &e.format {
33688                    let java_fmt = Self::strftime_to_java_format(format);
33689                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33690                        self.write(", '");
33691                        self.write(&java_fmt);
33692                        self.write("'");
33693                    }
33694                }
33695                self.write(")");
33696            }
33697            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33698                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33699                self.write_keyword("UNIX_TIMESTAMP");
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            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33712                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33713                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33714                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33715                let java_fmt = Self::strftime_to_java_format(c_fmt);
33716                self.write_keyword("TO_UNIXTIME");
33717                self.write("(");
33718                self.write_keyword("COALESCE");
33719                self.write("(");
33720                self.write_keyword("TRY");
33721                self.write("(");
33722                self.write_keyword("DATE_PARSE");
33723                self.write("(");
33724                self.write_keyword("CAST");
33725                self.write("(");
33726                if let Some(this) = &e.this {
33727                    self.generate_expression(this)?;
33728                }
33729                self.write(" ");
33730                self.write_keyword("AS VARCHAR");
33731                self.write("), '");
33732                self.write(c_fmt);
33733                self.write("')), ");
33734                self.write_keyword("PARSE_DATETIME");
33735                self.write("(");
33736                self.write_keyword("DATE_FORMAT");
33737                self.write("(");
33738                self.write_keyword("CAST");
33739                self.write("(");
33740                if let Some(this) = &e.this {
33741                    self.generate_expression(this)?;
33742                }
33743                self.write(" ");
33744                self.write_keyword("AS TIMESTAMP");
33745                self.write("), '");
33746                self.write(c_fmt);
33747                self.write("'), '");
33748                self.write(&java_fmt);
33749                self.write("')))");
33750            }
33751            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33752                // Spark: UNIX_TIMESTAMP(value, java_format)
33753                self.write_keyword("UNIX_TIMESTAMP");
33754                self.write("(");
33755                if let Some(this) = &e.this {
33756                    self.generate_expression(this)?;
33757                }
33758                if let Some(format) = &e.format {
33759                    let java_fmt = Self::strftime_to_java_format(format);
33760                    self.write(", '");
33761                    self.write(&java_fmt);
33762                    self.write("'");
33763                }
33764                self.write(")");
33765            }
33766            _ => {
33767                // Default: STR_TO_UNIX(this, format)
33768                self.write_keyword("STR_TO_UNIX");
33769                self.write("(");
33770                if let Some(this) = &e.this {
33771                    self.generate_expression(this)?;
33772                }
33773                if let Some(format) = &e.format {
33774                    self.write(", '");
33775                    self.write(format);
33776                    self.write("'");
33777                }
33778                self.write(")");
33779            }
33780        }
33781        Ok(())
33782    }
33783
33784    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33785        // STRING_TO_ARRAY(this, delimiter, null_string)
33786        self.write_keyword("STRING_TO_ARRAY");
33787        self.write("(");
33788        self.generate_expression(&e.this)?;
33789        if let Some(expression) = &e.expression {
33790            self.write(", ");
33791            self.generate_expression(expression)?;
33792        }
33793        if let Some(null_val) = &e.null {
33794            self.write(", ");
33795            self.generate_expression(null_val)?;
33796        }
33797        self.write(")");
33798        Ok(())
33799    }
33800
33801    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33802        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33803            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33804            self.write_keyword("OBJECT_CONSTRUCT");
33805            self.write("(");
33806            for (i, (name, expr)) in e.fields.iter().enumerate() {
33807                if i > 0 {
33808                    self.write(", ");
33809                }
33810                if let Some(name) = name {
33811                    self.write("'");
33812                    self.write(name);
33813                    self.write("'");
33814                    self.write(", ");
33815                } else {
33816                    self.write("'_");
33817                    self.write(&i.to_string());
33818                    self.write("'");
33819                    self.write(", ");
33820                }
33821                self.generate_expression(expr)?;
33822            }
33823            self.write(")");
33824        } else if self.config.struct_curly_brace_notation {
33825            // DuckDB-style: {'key': value, ...}
33826            self.write("{");
33827            for (i, (name, expr)) in e.fields.iter().enumerate() {
33828                if i > 0 {
33829                    self.write(", ");
33830                }
33831                if let Some(name) = name {
33832                    // Quote the key as a string literal
33833                    self.write("'");
33834                    self.write(name);
33835                    self.write("'");
33836                    self.write(": ");
33837                } else {
33838                    // Unnamed field: use positional key
33839                    self.write("'_");
33840                    self.write(&i.to_string());
33841                    self.write("'");
33842                    self.write(": ");
33843                }
33844                self.generate_expression(expr)?;
33845            }
33846            self.write("}");
33847        } else {
33848            // Standard SQL struct notation
33849            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33850            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33851            let value_as_name = matches!(
33852                self.config.dialect,
33853                Some(DialectType::BigQuery)
33854                    | Some(DialectType::Spark)
33855                    | Some(DialectType::Databricks)
33856                    | Some(DialectType::Hive)
33857            );
33858            self.write_keyword("STRUCT");
33859            self.write("(");
33860            for (i, (name, expr)) in e.fields.iter().enumerate() {
33861                if i > 0 {
33862                    self.write(", ");
33863                }
33864                if let Some(name) = name {
33865                    if value_as_name {
33866                        // STRUCT(value AS name)
33867                        self.generate_expression(expr)?;
33868                        self.write_space();
33869                        self.write_keyword("AS");
33870                        self.write_space();
33871                        // Quote name if it contains spaces or special chars
33872                        let needs_quoting = name.contains(' ') || name.contains('-');
33873                        if needs_quoting {
33874                            if matches!(
33875                                self.config.dialect,
33876                                Some(DialectType::Spark)
33877                                    | Some(DialectType::Databricks)
33878                                    | Some(DialectType::Hive)
33879                            ) {
33880                                self.write("`");
33881                                self.write(name);
33882                                self.write("`");
33883                            } else {
33884                                self.write(name);
33885                            }
33886                        } else {
33887                            self.write(name);
33888                        }
33889                    } else {
33890                        // STRUCT(name AS value)
33891                        self.write(name);
33892                        self.write_space();
33893                        self.write_keyword("AS");
33894                        self.write_space();
33895                        self.generate_expression(expr)?;
33896                    }
33897                } else {
33898                    self.generate_expression(expr)?;
33899                }
33900            }
33901            self.write(")");
33902        }
33903        Ok(())
33904    }
33905
33906    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33907        // STUFF(this, start, length, expression)
33908        self.write_keyword("STUFF");
33909        self.write("(");
33910        self.generate_expression(&e.this)?;
33911        if let Some(start) = &e.start {
33912            self.write(", ");
33913            self.generate_expression(start)?;
33914        }
33915        if let Some(length) = e.length {
33916            self.write(", ");
33917            self.write(&length.to_string());
33918        }
33919        self.write(", ");
33920        self.generate_expression(&e.expression)?;
33921        self.write(")");
33922        Ok(())
33923    }
33924
33925    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33926        // SUBSTRING_INDEX(this, delimiter, count)
33927        self.write_keyword("SUBSTRING_INDEX");
33928        self.write("(");
33929        self.generate_expression(&e.this)?;
33930        if let Some(delimiter) = &e.delimiter {
33931            self.write(", ");
33932            self.generate_expression(delimiter)?;
33933        }
33934        if let Some(count) = &e.count {
33935            self.write(", ");
33936            self.generate_expression(count)?;
33937        }
33938        self.write(")");
33939        Ok(())
33940    }
33941
33942    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33943        // SUMMARIZE [TABLE] this
33944        self.write_keyword("SUMMARIZE");
33945        if e.table.is_some() {
33946            self.write_space();
33947            self.write_keyword("TABLE");
33948        }
33949        self.write_space();
33950        self.generate_expression(&e.this)?;
33951        Ok(())
33952    }
33953
33954    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33955        // SYSTIMESTAMP
33956        self.write_keyword("SYSTIMESTAMP");
33957        Ok(())
33958    }
33959
33960    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33961        // alias (columns...)
33962        if let Some(this) = &e.this {
33963            self.generate_expression(this)?;
33964        }
33965        if !e.columns.is_empty() {
33966            self.write("(");
33967            for (i, col) in e.columns.iter().enumerate() {
33968                if i > 0 {
33969                    self.write(", ");
33970                }
33971                self.generate_expression(col)?;
33972            }
33973            self.write(")");
33974        }
33975        Ok(())
33976    }
33977
33978    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33979        // TABLE(this) [AS alias]
33980        self.write_keyword("TABLE");
33981        self.write("(");
33982        self.generate_expression(&e.this)?;
33983        self.write(")");
33984        if let Some(alias) = &e.alias {
33985            self.write_space();
33986            self.write_keyword("AS");
33987            self.write_space();
33988            self.write(alias);
33989        }
33990        Ok(())
33991    }
33992
33993    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33994        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33995        self.write_keyword("ROWS FROM");
33996        self.write(" (");
33997        for (i, expr) in e.expressions.iter().enumerate() {
33998            if i > 0 {
33999                self.write(", ");
34000            }
34001            // Each expression is either:
34002            // - A plain function (no alias)
34003            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
34004            match expr {
34005                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
34006                    // First element is the function, second is the TableAlias
34007                    self.generate_expression(&tuple.expressions[0])?;
34008                    self.write_space();
34009                    self.write_keyword("AS");
34010                    self.write_space();
34011                    self.generate_expression(&tuple.expressions[1])?;
34012                }
34013                _ => {
34014                    self.generate_expression(expr)?;
34015                }
34016            }
34017        }
34018        self.write(")");
34019        if e.ordinality {
34020            self.write_space();
34021            self.write_keyword("WITH ORDINALITY");
34022        }
34023        if let Some(alias) = &e.alias {
34024            self.write_space();
34025            self.write_keyword("AS");
34026            self.write_space();
34027            self.generate_expression(alias)?;
34028        }
34029        Ok(())
34030    }
34031
34032    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
34033        use crate::dialects::DialectType;
34034
34035        // New wrapper pattern: expression + Sample struct
34036        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
34037            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
34038            if self.config.alias_post_tablesample {
34039                // Handle Subquery with alias and Alias wrapper
34040                if let Expression::Subquery(ref s) = **this {
34041                    if let Some(ref alias) = s.alias {
34042                        // Create a clone without alias for output
34043                        let mut subquery_no_alias = (**s).clone();
34044                        subquery_no_alias.alias = None;
34045                        subquery_no_alias.column_aliases = Vec::new();
34046                        self.generate_expression(&Expression::Subquery(Box::new(
34047                            subquery_no_alias,
34048                        )))?;
34049                        self.write_space();
34050                        self.write_keyword("TABLESAMPLE");
34051                        self.generate_sample_body(sample)?;
34052                        if let Some(ref seed) = sample.seed {
34053                            self.write_space();
34054                            let use_seed = sample.use_seed_keyword
34055                                && !matches!(
34056                                    self.config.dialect,
34057                                    Some(crate::dialects::DialectType::Databricks)
34058                                        | Some(crate::dialects::DialectType::Spark)
34059                                );
34060                            if use_seed {
34061                                self.write_keyword("SEED");
34062                            } else {
34063                                self.write_keyword("REPEATABLE");
34064                            }
34065                            self.write(" (");
34066                            self.generate_expression(seed)?;
34067                            self.write(")");
34068                        }
34069                        self.write_space();
34070                        self.write_keyword("AS");
34071                        self.write_space();
34072                        self.generate_identifier(alias)?;
34073                        return Ok(());
34074                    }
34075                } else if let Expression::Alias(ref a) = **this {
34076                    // Output the base expression without alias
34077                    self.generate_expression(&a.this)?;
34078                    self.write_space();
34079                    self.write_keyword("TABLESAMPLE");
34080                    self.generate_sample_body(sample)?;
34081                    if let Some(ref seed) = sample.seed {
34082                        self.write_space();
34083                        let use_seed = sample.use_seed_keyword
34084                            && !matches!(
34085                                self.config.dialect,
34086                                Some(crate::dialects::DialectType::Databricks)
34087                                    | Some(crate::dialects::DialectType::Spark)
34088                            );
34089                        if use_seed {
34090                            self.write_keyword("SEED");
34091                        } else {
34092                            self.write_keyword("REPEATABLE");
34093                        }
34094                        self.write(" (");
34095                        self.generate_expression(seed)?;
34096                        self.write(")");
34097                    }
34098                    // Output alias after TABLESAMPLE
34099                    self.write_space();
34100                    self.write_keyword("AS");
34101                    self.write_space();
34102                    self.generate_identifier(&a.alias)?;
34103                    return Ok(());
34104                }
34105            }
34106            // Default: generate wrapped expression first, then TABLESAMPLE
34107            self.generate_expression(this)?;
34108            self.write_space();
34109            self.write_keyword("TABLESAMPLE");
34110            self.generate_sample_body(sample)?;
34111            // Seed for table-level sample
34112            if let Some(ref seed) = sample.seed {
34113                self.write_space();
34114                // Databricks uses REPEATABLE, not SEED
34115                let use_seed = sample.use_seed_keyword
34116                    && !matches!(
34117                        self.config.dialect,
34118                        Some(crate::dialects::DialectType::Databricks)
34119                            | Some(crate::dialects::DialectType::Spark)
34120                    );
34121                if use_seed {
34122                    self.write_keyword("SEED");
34123                } else {
34124                    self.write_keyword("REPEATABLE");
34125                }
34126                self.write(" (");
34127                self.generate_expression(seed)?;
34128                self.write(")");
34129            }
34130            return Ok(());
34131        }
34132
34133        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
34134        self.write_keyword("TABLESAMPLE");
34135        if let Some(method) = &e.method {
34136            self.write_space();
34137            self.write_keyword(method);
34138        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34139            // Snowflake defaults to BERNOULLI when no method is specified
34140            self.write_space();
34141            self.write_keyword("BERNOULLI");
34142        }
34143        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
34144            self.write_space();
34145            self.write_keyword("BUCKET");
34146            self.write_space();
34147            self.generate_expression(numerator)?;
34148            self.write_space();
34149            self.write_keyword("OUT OF");
34150            self.write_space();
34151            self.generate_expression(denominator)?;
34152            if let Some(field) = &e.bucket_field {
34153                self.write_space();
34154                self.write_keyword("ON");
34155                self.write_space();
34156                self.generate_expression(field)?;
34157            }
34158        } else if !e.expressions.is_empty() {
34159            self.write(" (");
34160            for (i, expr) in e.expressions.iter().enumerate() {
34161                if i > 0 {
34162                    self.write(", ");
34163                }
34164                self.generate_expression(expr)?;
34165            }
34166            self.write(")");
34167        } else if let Some(percent) = &e.percent {
34168            self.write(" (");
34169            self.generate_expression(percent)?;
34170            self.write_space();
34171            self.write_keyword("PERCENT");
34172            self.write(")");
34173        }
34174        Ok(())
34175    }
34176
34177    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
34178        // [prefix]this[postfix]
34179        if let Some(prefix) = &e.prefix {
34180            self.generate_expression(prefix)?;
34181        }
34182        if let Some(this) = &e.this {
34183            self.generate_expression(this)?;
34184        }
34185        if let Some(postfix) = &e.postfix {
34186            self.generate_expression(postfix)?;
34187        }
34188        Ok(())
34189    }
34190
34191    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
34192        // TAG (expressions)
34193        self.write_keyword("TAG");
34194        self.write(" (");
34195        for (i, expr) in e.expressions.iter().enumerate() {
34196            if i > 0 {
34197                self.write(", ");
34198            }
34199            self.generate_expression(expr)?;
34200        }
34201        self.write(")");
34202        Ok(())
34203    }
34204
34205    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
34206        // TEMPORARY or TEMP or [this] TEMPORARY
34207        if let Some(this) = &e.this {
34208            self.generate_expression(this)?;
34209            self.write_space();
34210        }
34211        self.write_keyword("TEMPORARY");
34212        Ok(())
34213    }
34214
34215    /// Generate a Time function expression
34216    /// For most dialects: TIME('value')
34217    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
34218        // Standard: TIME(value)
34219        self.write_keyword("TIME");
34220        self.write("(");
34221        self.generate_expression(&e.this)?;
34222        self.write(")");
34223        Ok(())
34224    }
34225
34226    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
34227        // TIME_ADD(this, expression, unit)
34228        self.write_keyword("TIME_ADD");
34229        self.write("(");
34230        self.generate_expression(&e.this)?;
34231        self.write(", ");
34232        self.generate_expression(&e.expression)?;
34233        if let Some(unit) = &e.unit {
34234            self.write(", ");
34235            self.write_keyword(unit);
34236        }
34237        self.write(")");
34238        Ok(())
34239    }
34240
34241    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
34242        // TIME_DIFF(this, expression, unit)
34243        self.write_keyword("TIME_DIFF");
34244        self.write("(");
34245        self.generate_expression(&e.this)?;
34246        self.write(", ");
34247        self.generate_expression(&e.expression)?;
34248        if let Some(unit) = &e.unit {
34249            self.write(", ");
34250            self.write_keyword(unit);
34251        }
34252        self.write(")");
34253        Ok(())
34254    }
34255
34256    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
34257        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
34258        self.write_keyword("TIME_FROM_PARTS");
34259        self.write("(");
34260        let mut first = true;
34261        if let Some(hour) = &e.hour {
34262            self.generate_expression(hour)?;
34263            first = false;
34264        }
34265        if let Some(minute) = &e.min {
34266            if !first {
34267                self.write(", ");
34268            }
34269            self.generate_expression(minute)?;
34270            first = false;
34271        }
34272        if let Some(second) = &e.sec {
34273            if !first {
34274                self.write(", ");
34275            }
34276            self.generate_expression(second)?;
34277            first = false;
34278        }
34279        if let Some(ns) = &e.nano {
34280            if !first {
34281                self.write(", ");
34282            }
34283            self.generate_expression(ns)?;
34284        }
34285        self.write(")");
34286        Ok(())
34287    }
34288
34289    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34290        // TIME_SLICE(this, expression, unit)
34291        self.write_keyword("TIME_SLICE");
34292        self.write("(");
34293        self.generate_expression(&e.this)?;
34294        self.write(", ");
34295        self.generate_expression(&e.expression)?;
34296        self.write(", ");
34297        self.write_keyword(&e.unit);
34298        self.write(")");
34299        Ok(())
34300    }
34301
34302    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34303        // TIME_STR_TO_TIME(this)
34304        self.write_keyword("TIME_STR_TO_TIME");
34305        self.write("(");
34306        self.generate_expression(&e.this)?;
34307        self.write(")");
34308        Ok(())
34309    }
34310
34311    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34312        // TIME_SUB(this, expression, unit)
34313        self.write_keyword("TIME_SUB");
34314        self.write("(");
34315        self.generate_expression(&e.this)?;
34316        self.write(", ");
34317        self.generate_expression(&e.expression)?;
34318        if let Some(unit) = &e.unit {
34319            self.write(", ");
34320            self.write_keyword(unit);
34321        }
34322        self.write(")");
34323        Ok(())
34324    }
34325
34326    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34327        match self.config.dialect {
34328            Some(DialectType::Exasol) => {
34329                // Exasol uses TO_CHAR with Exasol-specific format
34330                self.write_keyword("TO_CHAR");
34331                self.write("(");
34332                self.generate_expression(&e.this)?;
34333                self.write(", '");
34334                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34335                self.write("'");
34336                self.write(")");
34337            }
34338            Some(DialectType::PostgreSQL)
34339            | Some(DialectType::Redshift)
34340            | Some(DialectType::Materialize) => {
34341                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34342                self.write_keyword("TO_CHAR");
34343                self.write("(");
34344                self.generate_expression(&e.this)?;
34345                self.write(", '");
34346                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34347                self.write("'");
34348                self.write(")");
34349            }
34350            Some(DialectType::Oracle) => {
34351                // Oracle uses TO_CHAR with PG-like format
34352                self.write_keyword("TO_CHAR");
34353                self.write("(");
34354                self.generate_expression(&e.this)?;
34355                self.write(", '");
34356                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34357                self.write("'");
34358                self.write(")");
34359            }
34360            Some(DialectType::Drill) => {
34361                // Drill: TO_CHAR with Java format
34362                self.write_keyword("TO_CHAR");
34363                self.write("(");
34364                self.generate_expression(&e.this)?;
34365                self.write(", '");
34366                self.write(&Self::strftime_to_java_format(&e.format));
34367                self.write("'");
34368                self.write(")");
34369            }
34370            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34371                // TSQL: FORMAT(value, format) with .NET-style format
34372                self.write_keyword("FORMAT");
34373                self.write("(");
34374                self.generate_expression(&e.this)?;
34375                self.write(", '");
34376                self.write(&Self::strftime_to_tsql_format(&e.format));
34377                self.write("'");
34378                self.write(")");
34379            }
34380            Some(DialectType::DuckDB) => {
34381                // DuckDB: STRFTIME(value, format) - keeps C format
34382                self.write_keyword("STRFTIME");
34383                self.write("(");
34384                self.generate_expression(&e.this)?;
34385                self.write(", '");
34386                self.write(&e.format);
34387                self.write("'");
34388                self.write(")");
34389            }
34390            Some(DialectType::BigQuery) => {
34391                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34392                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34393                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34394                self.write_keyword("FORMAT_DATE");
34395                self.write("('");
34396                self.write(&fmt);
34397                self.write("', ");
34398                self.generate_expression(&e.this)?;
34399                self.write(")");
34400            }
34401            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34402                // Hive/Spark: DATE_FORMAT(value, java_format)
34403                self.write_keyword("DATE_FORMAT");
34404                self.write("(");
34405                self.generate_expression(&e.this)?;
34406                self.write(", '");
34407                self.write(&Self::strftime_to_java_format(&e.format));
34408                self.write("'");
34409                self.write(")");
34410            }
34411            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34412                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34413                self.write_keyword("DATE_FORMAT");
34414                self.write("(");
34415                self.generate_expression(&e.this)?;
34416                self.write(", '");
34417                self.write(&e.format);
34418                self.write("'");
34419                self.write(")");
34420            }
34421            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34422                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34423                self.write_keyword("DATE_FORMAT");
34424                self.write("(");
34425                self.generate_expression(&e.this)?;
34426                self.write(", '");
34427                self.write(&e.format);
34428                self.write("'");
34429                self.write(")");
34430            }
34431            _ => {
34432                // Default: TIME_TO_STR(this, format)
34433                self.write_keyword("TIME_TO_STR");
34434                self.write("(");
34435                self.generate_expression(&e.this)?;
34436                self.write(", '");
34437                self.write(&e.format);
34438                self.write("'");
34439                self.write(")");
34440            }
34441        }
34442        Ok(())
34443    }
34444
34445    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34446        match self.config.dialect {
34447            Some(DialectType::DuckDB) => {
34448                // DuckDB: EPOCH(x)
34449                self.write_keyword("EPOCH");
34450                self.write("(");
34451                self.generate_expression(&e.this)?;
34452                self.write(")");
34453            }
34454            Some(DialectType::Hive)
34455            | Some(DialectType::Spark)
34456            | Some(DialectType::Databricks)
34457            | Some(DialectType::Doris)
34458            | Some(DialectType::StarRocks)
34459            | Some(DialectType::Drill) => {
34460                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34461                self.write_keyword("UNIX_TIMESTAMP");
34462                self.write("(");
34463                self.generate_expression(&e.this)?;
34464                self.write(")");
34465            }
34466            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34467                // Presto: TO_UNIXTIME(x)
34468                self.write_keyword("TO_UNIXTIME");
34469                self.write("(");
34470                self.generate_expression(&e.this)?;
34471                self.write(")");
34472            }
34473            _ => {
34474                // Default: TIME_TO_UNIX(x)
34475                self.write_keyword("TIME_TO_UNIX");
34476                self.write("(");
34477                self.generate_expression(&e.this)?;
34478                self.write(")");
34479            }
34480        }
34481        Ok(())
34482    }
34483
34484    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34485        match self.config.dialect {
34486            Some(DialectType::Hive) => {
34487                // Hive: TO_DATE(x)
34488                self.write_keyword("TO_DATE");
34489                self.write("(");
34490                self.generate_expression(&e.this)?;
34491                self.write(")");
34492            }
34493            _ => {
34494                // Default: TIME_STR_TO_DATE(x)
34495                self.write_keyword("TIME_STR_TO_DATE");
34496                self.write("(");
34497                self.generate_expression(&e.this)?;
34498                self.write(")");
34499            }
34500        }
34501        Ok(())
34502    }
34503
34504    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34505        // TIME_TRUNC(this, unit)
34506        self.write_keyword("TIME_TRUNC");
34507        self.write("(");
34508        self.generate_expression(&e.this)?;
34509        self.write(", ");
34510        self.write_keyword(&e.unit);
34511        self.write(")");
34512        Ok(())
34513    }
34514
34515    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34516        // Just output the unit name
34517        if let Some(unit) = &e.unit {
34518            self.write_keyword(unit);
34519        }
34520        Ok(())
34521    }
34522
34523    /// Generate a Timestamp function expression
34524    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34525    /// For other dialects: TIMESTAMP('value')
34526    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34527        use crate::dialects::DialectType;
34528        use crate::expressions::Literal;
34529
34530        match self.config.dialect {
34531            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34532            Some(DialectType::Exasol) => {
34533                self.write_keyword("TO_TIMESTAMP");
34534                self.write("(");
34535                // Extract the string value from the expression if it's a string literal
34536                if let Some(this) = &e.this {
34537                    match this.as_ref() {
34538                        Expression::Literal(Literal::String(s)) => {
34539                            self.write("'");
34540                            self.write(s);
34541                            self.write("'");
34542                        }
34543                        _ => {
34544                            self.generate_expression(this)?;
34545                        }
34546                    }
34547                }
34548                self.write(")");
34549            }
34550            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34551            _ => {
34552                self.write_keyword("TIMESTAMP");
34553                self.write("(");
34554                if let Some(this) = &e.this {
34555                    self.generate_expression(this)?;
34556                }
34557                if let Some(zone) = &e.zone {
34558                    self.write(", ");
34559                    self.generate_expression(zone)?;
34560                }
34561                self.write(")");
34562            }
34563        }
34564        Ok(())
34565    }
34566
34567    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34568        // TIMESTAMP_ADD(this, expression, unit)
34569        self.write_keyword("TIMESTAMP_ADD");
34570        self.write("(");
34571        self.generate_expression(&e.this)?;
34572        self.write(", ");
34573        self.generate_expression(&e.expression)?;
34574        if let Some(unit) = &e.unit {
34575            self.write(", ");
34576            self.write_keyword(unit);
34577        }
34578        self.write(")");
34579        Ok(())
34580    }
34581
34582    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34583        // TIMESTAMP_DIFF(this, expression, unit)
34584        self.write_keyword("TIMESTAMP_DIFF");
34585        self.write("(");
34586        self.generate_expression(&e.this)?;
34587        self.write(", ");
34588        self.generate_expression(&e.expression)?;
34589        if let Some(unit) = &e.unit {
34590            self.write(", ");
34591            self.write_keyword(unit);
34592        }
34593        self.write(")");
34594        Ok(())
34595    }
34596
34597    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34598        // TIMESTAMP_FROM_PARTS(this, expression)
34599        self.write_keyword("TIMESTAMP_FROM_PARTS");
34600        self.write("(");
34601        if let Some(this) = &e.this {
34602            self.generate_expression(this)?;
34603        }
34604        if let Some(expression) = &e.expression {
34605            self.write(", ");
34606            self.generate_expression(expression)?;
34607        }
34608        if let Some(zone) = &e.zone {
34609            self.write(", ");
34610            self.generate_expression(zone)?;
34611        }
34612        if let Some(milli) = &e.milli {
34613            self.write(", ");
34614            self.generate_expression(milli)?;
34615        }
34616        self.write(")");
34617        Ok(())
34618    }
34619
34620    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34621        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34622        self.write_keyword("TIMESTAMP_SUB");
34623        self.write("(");
34624        self.generate_expression(&e.this)?;
34625        self.write(", ");
34626        self.write_keyword("INTERVAL");
34627        self.write_space();
34628        self.generate_expression(&e.expression)?;
34629        if let Some(unit) = &e.unit {
34630            self.write_space();
34631            self.write_keyword(unit);
34632        }
34633        self.write(")");
34634        Ok(())
34635    }
34636
34637    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34638        // TIMESTAMP_TZ_FROM_PARTS(...)
34639        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34640        self.write("(");
34641        if let Some(zone) = &e.zone {
34642            self.generate_expression(zone)?;
34643        }
34644        self.write(")");
34645        Ok(())
34646    }
34647
34648    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34649        // TO_BINARY(this, [format])
34650        self.write_keyword("TO_BINARY");
34651        self.write("(");
34652        self.generate_expression(&e.this)?;
34653        if let Some(format) = &e.format {
34654            self.write(", '");
34655            self.write(format);
34656            self.write("'");
34657        }
34658        self.write(")");
34659        Ok(())
34660    }
34661
34662    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34663        // TO_BOOLEAN(this)
34664        self.write_keyword("TO_BOOLEAN");
34665        self.write("(");
34666        self.generate_expression(&e.this)?;
34667        self.write(")");
34668        Ok(())
34669    }
34670
34671    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34672        // TO_CHAR(this, [format], [nlsparam])
34673        self.write_keyword("TO_CHAR");
34674        self.write("(");
34675        self.generate_expression(&e.this)?;
34676        if let Some(format) = &e.format {
34677            self.write(", '");
34678            self.write(format);
34679            self.write("'");
34680        }
34681        if let Some(nlsparam) = &e.nlsparam {
34682            self.write(", ");
34683            self.generate_expression(nlsparam)?;
34684        }
34685        self.write(")");
34686        Ok(())
34687    }
34688
34689    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34690        // TO_DECFLOAT(this, [format])
34691        self.write_keyword("TO_DECFLOAT");
34692        self.write("(");
34693        self.generate_expression(&e.this)?;
34694        if let Some(format) = &e.format {
34695            self.write(", '");
34696            self.write(format);
34697            self.write("'");
34698        }
34699        self.write(")");
34700        Ok(())
34701    }
34702
34703    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34704        // TO_DOUBLE(this, [format])
34705        self.write_keyword("TO_DOUBLE");
34706        self.write("(");
34707        self.generate_expression(&e.this)?;
34708        if let Some(format) = &e.format {
34709            self.write(", '");
34710            self.write(format);
34711            self.write("'");
34712        }
34713        self.write(")");
34714        Ok(())
34715    }
34716
34717    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34718        // TO_FILE(this, path)
34719        self.write_keyword("TO_FILE");
34720        self.write("(");
34721        self.generate_expression(&e.this)?;
34722        if let Some(path) = &e.path {
34723            self.write(", ");
34724            self.generate_expression(path)?;
34725        }
34726        self.write(")");
34727        Ok(())
34728    }
34729
34730    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34731        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34732        // If safe flag is set, output TRY_TO_NUMBER
34733        let is_safe = e.safe.is_some();
34734        if is_safe {
34735            self.write_keyword("TRY_TO_NUMBER");
34736        } else {
34737            self.write_keyword("TO_NUMBER");
34738        }
34739        self.write("(");
34740        self.generate_expression(&e.this)?;
34741        if let Some(format) = &e.format {
34742            self.write(", ");
34743            self.generate_expression(format)?;
34744        }
34745        if let Some(nlsparam) = &e.nlsparam {
34746            self.write(", ");
34747            self.generate_expression(nlsparam)?;
34748        }
34749        if let Some(precision) = &e.precision {
34750            self.write(", ");
34751            self.generate_expression(precision)?;
34752        }
34753        if let Some(scale) = &e.scale {
34754            self.write(", ");
34755            self.generate_expression(scale)?;
34756        }
34757        self.write(")");
34758        Ok(())
34759    }
34760
34761    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34762        // TO_TABLE this
34763        self.write_keyword("TO_TABLE");
34764        self.write_space();
34765        self.generate_expression(&e.this)?;
34766        Ok(())
34767    }
34768
34769    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34770        // Check mark to determine the format
34771        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34772            Expression::Identifier(id) => id.name.clone(),
34773            Expression::Literal(Literal::String(s)) => s.clone(),
34774            _ => String::new(),
34775        });
34776
34777        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34778        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34779        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34780            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34781        });
34782
34783        // For Presto/Trino: always use START TRANSACTION
34784        let use_start_transaction = matches!(
34785            self.config.dialect,
34786            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34787        );
34788        // For most dialects: strip TRANSACTION keyword
34789        let strip_transaction = matches!(
34790            self.config.dialect,
34791            Some(DialectType::Snowflake)
34792                | Some(DialectType::PostgreSQL)
34793                | Some(DialectType::Redshift)
34794                | Some(DialectType::MySQL)
34795                | Some(DialectType::Hive)
34796                | Some(DialectType::Spark)
34797                | Some(DialectType::Databricks)
34798                | Some(DialectType::DuckDB)
34799                | Some(DialectType::Oracle)
34800                | Some(DialectType::Doris)
34801                | Some(DialectType::StarRocks)
34802                | Some(DialectType::Materialize)
34803                | Some(DialectType::ClickHouse)
34804        );
34805
34806        if is_start || use_start_transaction {
34807            // START TRANSACTION [modes]
34808            self.write_keyword("START TRANSACTION");
34809            if let Some(modes) = &e.modes {
34810                self.write_space();
34811                self.generate_expression(modes)?;
34812            }
34813        } else {
34814            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34815            self.write_keyword("BEGIN");
34816
34817            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34818            let is_kind = e.this.as_ref().map_or(false, |t| {
34819                if let Expression::Identifier(id) = t.as_ref() {
34820                    matches!(
34821                        id.name.to_uppercase().as_str(),
34822                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34823                    )
34824                } else {
34825                    false
34826                }
34827            });
34828
34829            // Output kind before TRANSACTION keyword
34830            if is_kind {
34831                if let Some(this) = &e.this {
34832                    self.write_space();
34833                    if let Expression::Identifier(id) = this.as_ref() {
34834                        self.write_keyword(&id.name);
34835                    }
34836                }
34837            }
34838
34839            // Output TRANSACTION keyword if it was present and target supports it
34840            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34841                self.write_space();
34842                self.write_keyword("TRANSACTION");
34843            }
34844
34845            // Output transaction name (not kind)
34846            if !is_kind {
34847                if let Some(this) = &e.this {
34848                    self.write_space();
34849                    self.generate_expression(this)?;
34850                }
34851            }
34852
34853            // Output WITH MARK 'description' for TSQL
34854            if has_with_mark {
34855                self.write_space();
34856                self.write_keyword("WITH MARK");
34857                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34858                    if !desc.is_empty() {
34859                        self.write_space();
34860                        self.write(&format!("'{}'", desc));
34861                    }
34862                }
34863            }
34864
34865            // Output modes (isolation levels, etc.)
34866            if let Some(modes) = &e.modes {
34867                self.write_space();
34868                self.generate_expression(modes)?;
34869            }
34870        }
34871        Ok(())
34872    }
34873
34874    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34875        // TRANSFORM(this, expression)
34876        self.write_keyword("TRANSFORM");
34877        self.write("(");
34878        self.generate_expression(&e.this)?;
34879        self.write(", ");
34880        self.generate_expression(&e.expression)?;
34881        self.write(")");
34882        Ok(())
34883    }
34884
34885    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34886        // TRANSFORM(expressions)
34887        self.write_keyword("TRANSFORM");
34888        self.write("(");
34889        if self.config.pretty && !e.expressions.is_empty() {
34890            self.indent_level += 1;
34891            for (i, expr) in e.expressions.iter().enumerate() {
34892                if i > 0 {
34893                    self.write(",");
34894                }
34895                self.write_newline();
34896                self.write_indent();
34897                self.generate_expression(expr)?;
34898            }
34899            self.indent_level -= 1;
34900            self.write_newline();
34901            self.write(")");
34902        } else {
34903            for (i, expr) in e.expressions.iter().enumerate() {
34904                if i > 0 {
34905                    self.write(", ");
34906                }
34907                self.generate_expression(expr)?;
34908            }
34909            self.write(")");
34910        }
34911        Ok(())
34912    }
34913
34914    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34915        use crate::dialects::DialectType;
34916        // TRANSIENT is Snowflake-specific; skip for other dialects
34917        if let Some(this) = &e.this {
34918            self.generate_expression(this)?;
34919            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34920                self.write_space();
34921            }
34922        }
34923        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34924            self.write_keyword("TRANSIENT");
34925        }
34926        Ok(())
34927    }
34928
34929    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34930        // TRANSLATE(this, from_, to)
34931        self.write_keyword("TRANSLATE");
34932        self.write("(");
34933        self.generate_expression(&e.this)?;
34934        if let Some(from) = &e.from_ {
34935            self.write(", ");
34936            self.generate_expression(from)?;
34937        }
34938        if let Some(to) = &e.to {
34939            self.write(", ");
34940            self.generate_expression(to)?;
34941        }
34942        self.write(")");
34943        Ok(())
34944    }
34945
34946    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34947        // TRANSLATE(this USING expression)
34948        self.write_keyword("TRANSLATE");
34949        self.write("(");
34950        self.generate_expression(&e.this)?;
34951        self.write_space();
34952        self.write_keyword("USING");
34953        self.write_space();
34954        self.generate_expression(&e.expression)?;
34955        if e.with_error.is_some() {
34956            self.write_space();
34957            self.write_keyword("WITH ERROR");
34958        }
34959        self.write(")");
34960        Ok(())
34961    }
34962
34963    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34964        // TRUNCATE TABLE table1, table2, ...
34965        self.write_keyword("TRUNCATE TABLE");
34966        self.write_space();
34967        for (i, expr) in e.expressions.iter().enumerate() {
34968            if i > 0 {
34969                self.write(", ");
34970            }
34971            self.generate_expression(expr)?;
34972        }
34973        Ok(())
34974    }
34975
34976    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34977        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34978        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34979        self.write("(");
34980        self.generate_expression(&e.this)?;
34981        if let Some(alphabet) = &e.alphabet {
34982            self.write(", ");
34983            self.generate_expression(alphabet)?;
34984        }
34985        self.write(")");
34986        Ok(())
34987    }
34988
34989    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34990        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34991        self.write_keyword("TRY_BASE64_DECODE_STRING");
34992        self.write("(");
34993        self.generate_expression(&e.this)?;
34994        if let Some(alphabet) = &e.alphabet {
34995            self.write(", ");
34996            self.generate_expression(alphabet)?;
34997        }
34998        self.write(")");
34999        Ok(())
35000    }
35001
35002    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
35003        // TRY_TO_DECFLOAT(this, [format])
35004        self.write_keyword("TRY_TO_DECFLOAT");
35005        self.write("(");
35006        self.generate_expression(&e.this)?;
35007        if let Some(format) = &e.format {
35008            self.write(", '");
35009            self.write(format);
35010            self.write("'");
35011        }
35012        self.write(")");
35013        Ok(())
35014    }
35015
35016    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
35017        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
35018        self.write_keyword("TS_OR_DS_ADD");
35019        self.write("(");
35020        self.generate_expression(&e.this)?;
35021        self.write(", ");
35022        self.generate_expression(&e.expression)?;
35023        if let Some(unit) = &e.unit {
35024            self.write(", ");
35025            self.write_keyword(unit);
35026        }
35027        if let Some(return_type) = &e.return_type {
35028            self.write(", ");
35029            self.generate_expression(return_type)?;
35030        }
35031        self.write(")");
35032        Ok(())
35033    }
35034
35035    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
35036        // TS_OR_DS_DIFF(this, expression, [unit])
35037        self.write_keyword("TS_OR_DS_DIFF");
35038        self.write("(");
35039        self.generate_expression(&e.this)?;
35040        self.write(", ");
35041        self.generate_expression(&e.expression)?;
35042        if let Some(unit) = &e.unit {
35043            self.write(", ");
35044            self.write_keyword(unit);
35045        }
35046        self.write(")");
35047        Ok(())
35048    }
35049
35050    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
35051        let default_time_format = "%Y-%m-%d %H:%M:%S";
35052        let default_date_format = "%Y-%m-%d";
35053        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
35054            f != default_time_format && f != default_date_format
35055        });
35056
35057        if has_non_default_format {
35058            // With non-default format: dialect-specific handling
35059            let fmt = e.format.as_ref().unwrap();
35060            match self.config.dialect {
35061                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
35062                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
35063                    // STR_TO_DATE is the MySQL-native form of StrToTime
35064                    let str_to_time = crate::expressions::StrToTime {
35065                        this: Box::new((*e.this).clone()),
35066                        format: fmt.clone(),
35067                        zone: None,
35068                        safe: None,
35069                        target_type: None,
35070                    };
35071                    self.generate_str_to_time(&str_to_time)?;
35072                }
35073                Some(DialectType::Hive)
35074                | Some(DialectType::Spark)
35075                | Some(DialectType::Databricks) => {
35076                    // Hive/Spark: TO_DATE(x, java_fmt)
35077                    self.write_keyword("TO_DATE");
35078                    self.write("(");
35079                    self.generate_expression(&e.this)?;
35080                    self.write(", '");
35081                    self.write(&Self::strftime_to_java_format(fmt));
35082                    self.write("')");
35083                }
35084                Some(DialectType::Snowflake) => {
35085                    // Snowflake: TO_DATE(x, snowflake_fmt)
35086                    self.write_keyword("TO_DATE");
35087                    self.write("(");
35088                    self.generate_expression(&e.this)?;
35089                    self.write(", '");
35090                    self.write(&Self::strftime_to_snowflake_format(fmt));
35091                    self.write("')");
35092                }
35093                Some(DialectType::Doris) => {
35094                    // Doris: TO_DATE(x) - ignores format
35095                    self.write_keyword("TO_DATE");
35096                    self.write("(");
35097                    self.generate_expression(&e.this)?;
35098                    self.write(")");
35099                }
35100                _ => {
35101                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
35102                    self.write_keyword("CAST");
35103                    self.write("(");
35104                    let str_to_time = crate::expressions::StrToTime {
35105                        this: Box::new((*e.this).clone()),
35106                        format: fmt.clone(),
35107                        zone: None,
35108                        safe: None,
35109                        target_type: None,
35110                    };
35111                    self.generate_str_to_time(&str_to_time)?;
35112                    self.write_keyword(" AS ");
35113                    self.write_keyword("DATE");
35114                    self.write(")");
35115                }
35116            }
35117        } else {
35118            // Without format (or default format): simple date conversion
35119            match self.config.dialect {
35120                Some(DialectType::MySQL)
35121                | Some(DialectType::SQLite)
35122                | Some(DialectType::StarRocks) => {
35123                    // MySQL/SQLite/StarRocks: DATE(x)
35124                    self.write_keyword("DATE");
35125                    self.write("(");
35126                    self.generate_expression(&e.this)?;
35127                    self.write(")");
35128                }
35129                Some(DialectType::Hive)
35130                | Some(DialectType::Spark)
35131                | Some(DialectType::Databricks)
35132                | Some(DialectType::Snowflake)
35133                | Some(DialectType::Doris) => {
35134                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
35135                    self.write_keyword("TO_DATE");
35136                    self.write("(");
35137                    self.generate_expression(&e.this)?;
35138                    self.write(")");
35139                }
35140                Some(DialectType::Presto)
35141                | Some(DialectType::Trino)
35142                | Some(DialectType::Athena) => {
35143                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
35144                    self.write_keyword("CAST");
35145                    self.write("(");
35146                    self.write_keyword("CAST");
35147                    self.write("(");
35148                    self.generate_expression(&e.this)?;
35149                    self.write_keyword(" AS ");
35150                    self.write_keyword("TIMESTAMP");
35151                    self.write(")");
35152                    self.write_keyword(" AS ");
35153                    self.write_keyword("DATE");
35154                    self.write(")");
35155                }
35156                Some(DialectType::ClickHouse) => {
35157                    // ClickHouse: CAST(x AS Nullable(DATE))
35158                    self.write_keyword("CAST");
35159                    self.write("(");
35160                    self.generate_expression(&e.this)?;
35161                    self.write_keyword(" AS ");
35162                    self.write("Nullable(DATE)");
35163                    self.write(")");
35164                }
35165                _ => {
35166                    // Default: CAST(x AS DATE)
35167                    self.write_keyword("CAST");
35168                    self.write("(");
35169                    self.generate_expression(&e.this)?;
35170                    self.write_keyword(" AS ");
35171                    self.write_keyword("DATE");
35172                    self.write(")");
35173                }
35174            }
35175        }
35176        Ok(())
35177    }
35178
35179    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
35180        // TS_OR_DS_TO_TIME(this, [format])
35181        self.write_keyword("TS_OR_DS_TO_TIME");
35182        self.write("(");
35183        self.generate_expression(&e.this)?;
35184        if let Some(format) = &e.format {
35185            self.write(", '");
35186            self.write(format);
35187            self.write("'");
35188        }
35189        self.write(")");
35190        Ok(())
35191    }
35192
35193    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
35194        // UNHEX(this, [expression])
35195        self.write_keyword("UNHEX");
35196        self.write("(");
35197        self.generate_expression(&e.this)?;
35198        if let Some(expression) = &e.expression {
35199            self.write(", ");
35200            self.generate_expression(expression)?;
35201        }
35202        self.write(")");
35203        Ok(())
35204    }
35205
35206    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
35207        // U&this [UESCAPE escape]
35208        self.write("U&");
35209        self.generate_expression(&e.this)?;
35210        if let Some(escape) = &e.escape {
35211            self.write_space();
35212            self.write_keyword("UESCAPE");
35213            self.write_space();
35214            self.generate_expression(escape)?;
35215        }
35216        Ok(())
35217    }
35218
35219    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
35220        // UNIFORM(this, expression, [gen], [seed])
35221        self.write_keyword("UNIFORM");
35222        self.write("(");
35223        self.generate_expression(&e.this)?;
35224        self.write(", ");
35225        self.generate_expression(&e.expression)?;
35226        if let Some(gen) = &e.gen {
35227            self.write(", ");
35228            self.generate_expression(gen)?;
35229        }
35230        if let Some(seed) = &e.seed {
35231            self.write(", ");
35232            self.generate_expression(seed)?;
35233        }
35234        self.write(")");
35235        Ok(())
35236    }
35237
35238    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
35239        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
35240        self.write_keyword("UNIQUE");
35241        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
35242        if e.nulls.is_some() {
35243            self.write(" NULLS NOT DISTINCT");
35244        }
35245        if let Some(this) = &e.this {
35246            self.write_space();
35247            self.generate_expression(this)?;
35248        }
35249        if let Some(index_type) = &e.index_type {
35250            self.write(" USING ");
35251            self.generate_expression(index_type)?;
35252        }
35253        if let Some(on_conflict) = &e.on_conflict {
35254            self.write_space();
35255            self.generate_expression(on_conflict)?;
35256        }
35257        for opt in &e.options {
35258            self.write_space();
35259            self.generate_expression(opt)?;
35260        }
35261        Ok(())
35262    }
35263
35264    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
35265        // UNIQUE KEY (expressions)
35266        self.write_keyword("UNIQUE KEY");
35267        self.write(" (");
35268        for (i, expr) in e.expressions.iter().enumerate() {
35269            if i > 0 {
35270                self.write(", ");
35271            }
35272            self.generate_expression(expr)?;
35273        }
35274        self.write(")");
35275        Ok(())
35276    }
35277
35278    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
35279        // ROLLUP (r1(col1, col2), r2(col1))
35280        self.write_keyword("ROLLUP");
35281        self.write(" (");
35282        for (i, index) in e.expressions.iter().enumerate() {
35283            if i > 0 {
35284                self.write(", ");
35285            }
35286            self.generate_identifier(&index.name)?;
35287            self.write("(");
35288            for (j, col) in index.expressions.iter().enumerate() {
35289                if j > 0 {
35290                    self.write(", ");
35291                }
35292                self.generate_identifier(col)?;
35293            }
35294            self.write(")");
35295        }
35296        self.write(")");
35297        Ok(())
35298    }
35299
35300    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35301        match self.config.dialect {
35302            Some(DialectType::DuckDB) => {
35303                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35304                self.write_keyword("STRFTIME");
35305                self.write("(");
35306                self.write_keyword("TO_TIMESTAMP");
35307                self.write("(");
35308                self.generate_expression(&e.this)?;
35309                self.write("), '");
35310                if let Some(format) = &e.format {
35311                    self.write(format);
35312                }
35313                self.write("')");
35314            }
35315            Some(DialectType::Hive) => {
35316                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35317                self.write_keyword("FROM_UNIXTIME");
35318                self.write("(");
35319                self.generate_expression(&e.this)?;
35320                if let Some(format) = &e.format {
35321                    if format != "yyyy-MM-dd HH:mm:ss" {
35322                        self.write(", '");
35323                        self.write(format);
35324                        self.write("'");
35325                    }
35326                }
35327                self.write(")");
35328            }
35329            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35330                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35331                self.write_keyword("DATE_FORMAT");
35332                self.write("(");
35333                self.write_keyword("FROM_UNIXTIME");
35334                self.write("(");
35335                self.generate_expression(&e.this)?;
35336                self.write("), '");
35337                if let Some(format) = &e.format {
35338                    self.write(format);
35339                }
35340                self.write("')");
35341            }
35342            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35343                // Spark: FROM_UNIXTIME(value, format)
35344                self.write_keyword("FROM_UNIXTIME");
35345                self.write("(");
35346                self.generate_expression(&e.this)?;
35347                if let Some(format) = &e.format {
35348                    self.write(", '");
35349                    self.write(format);
35350                    self.write("'");
35351                }
35352                self.write(")");
35353            }
35354            _ => {
35355                // Default: UNIX_TO_STR(this, [format])
35356                self.write_keyword("UNIX_TO_STR");
35357                self.write("(");
35358                self.generate_expression(&e.this)?;
35359                if let Some(format) = &e.format {
35360                    self.write(", '");
35361                    self.write(format);
35362                    self.write("'");
35363                }
35364                self.write(")");
35365            }
35366        }
35367        Ok(())
35368    }
35369
35370    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35371        use crate::dialects::DialectType;
35372        let scale = e.scale.unwrap_or(0); // 0 = seconds
35373
35374        match self.config.dialect {
35375            Some(DialectType::Snowflake) => {
35376                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35377                self.write_keyword("TO_TIMESTAMP");
35378                self.write("(");
35379                self.generate_expression(&e.this)?;
35380                if let Some(s) = e.scale {
35381                    if s > 0 {
35382                        self.write(", ");
35383                        self.write(&s.to_string());
35384                    }
35385                }
35386                self.write(")");
35387            }
35388            Some(DialectType::BigQuery) => {
35389                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35390                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35391                match scale {
35392                    0 => {
35393                        self.write_keyword("TIMESTAMP_SECONDS");
35394                        self.write("(");
35395                        self.generate_expression(&e.this)?;
35396                        self.write(")");
35397                    }
35398                    3 => {
35399                        self.write_keyword("TIMESTAMP_MILLIS");
35400                        self.write("(");
35401                        self.generate_expression(&e.this)?;
35402                        self.write(")");
35403                    }
35404                    6 => {
35405                        self.write_keyword("TIMESTAMP_MICROS");
35406                        self.write("(");
35407                        self.generate_expression(&e.this)?;
35408                        self.write(")");
35409                    }
35410                    _ => {
35411                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35412                        self.write_keyword("TIMESTAMP_SECONDS");
35413                        self.write("(CAST(");
35414                        self.generate_expression(&e.this)?;
35415                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35416                    }
35417                }
35418            }
35419            Some(DialectType::Spark) => {
35420                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35421                // TIMESTAMP_MILLIS(value) for scale=3
35422                // TIMESTAMP_MICROS(value) for scale=6
35423                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35424                match scale {
35425                    0 => {
35426                        self.write_keyword("CAST");
35427                        self.write("(");
35428                        self.write_keyword("FROM_UNIXTIME");
35429                        self.write("(");
35430                        self.generate_expression(&e.this)?;
35431                        self.write(") ");
35432                        self.write_keyword("AS TIMESTAMP");
35433                        self.write(")");
35434                    }
35435                    3 => {
35436                        self.write_keyword("TIMESTAMP_MILLIS");
35437                        self.write("(");
35438                        self.generate_expression(&e.this)?;
35439                        self.write(")");
35440                    }
35441                    6 => {
35442                        self.write_keyword("TIMESTAMP_MICROS");
35443                        self.write("(");
35444                        self.generate_expression(&e.this)?;
35445                        self.write(")");
35446                    }
35447                    _ => {
35448                        self.write_keyword("TIMESTAMP_SECONDS");
35449                        self.write("(");
35450                        self.generate_expression(&e.this)?;
35451                        self.write(&format!(" / POWER(10, {}))", scale));
35452                    }
35453                }
35454            }
35455            Some(DialectType::Databricks) => {
35456                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35457                // TIMESTAMP_MILLIS(value) for scale=3
35458                // TIMESTAMP_MICROS(value) for scale=6
35459                match scale {
35460                    0 => {
35461                        self.write_keyword("CAST");
35462                        self.write("(");
35463                        self.write_keyword("FROM_UNIXTIME");
35464                        self.write("(");
35465                        self.generate_expression(&e.this)?;
35466                        self.write(") ");
35467                        self.write_keyword("AS TIMESTAMP");
35468                        self.write(")");
35469                    }
35470                    3 => {
35471                        self.write_keyword("TIMESTAMP_MILLIS");
35472                        self.write("(");
35473                        self.generate_expression(&e.this)?;
35474                        self.write(")");
35475                    }
35476                    6 => {
35477                        self.write_keyword("TIMESTAMP_MICROS");
35478                        self.write("(");
35479                        self.generate_expression(&e.this)?;
35480                        self.write(")");
35481                    }
35482                    _ => {
35483                        self.write_keyword("TIMESTAMP_SECONDS");
35484                        self.write("(");
35485                        self.generate_expression(&e.this)?;
35486                        self.write(&format!(" / POWER(10, {}))", scale));
35487                    }
35488                }
35489            }
35490            Some(DialectType::Hive) => {
35491                // Hive: FROM_UNIXTIME(value)
35492                if scale == 0 {
35493                    self.write_keyword("FROM_UNIXTIME");
35494                    self.write("(");
35495                    self.generate_expression(&e.this)?;
35496                    self.write(")");
35497                } else {
35498                    self.write_keyword("FROM_UNIXTIME");
35499                    self.write("(");
35500                    self.generate_expression(&e.this)?;
35501                    self.write(&format!(" / POWER(10, {})", scale));
35502                    self.write(")");
35503                }
35504            }
35505            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35506                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35507                // FROM_UNIXTIME(value) for scale=0
35508                if scale == 0 {
35509                    self.write_keyword("FROM_UNIXTIME");
35510                    self.write("(");
35511                    self.generate_expression(&e.this)?;
35512                    self.write(")");
35513                } else {
35514                    self.write_keyword("FROM_UNIXTIME");
35515                    self.write("(CAST(");
35516                    self.generate_expression(&e.this)?;
35517                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35518                }
35519            }
35520            Some(DialectType::DuckDB) => {
35521                // DuckDB: TO_TIMESTAMP(value) for scale=0
35522                // EPOCH_MS(value) for scale=3
35523                // MAKE_TIMESTAMP(value) for scale=6
35524                match scale {
35525                    0 => {
35526                        self.write_keyword("TO_TIMESTAMP");
35527                        self.write("(");
35528                        self.generate_expression(&e.this)?;
35529                        self.write(")");
35530                    }
35531                    3 => {
35532                        self.write_keyword("EPOCH_MS");
35533                        self.write("(");
35534                        self.generate_expression(&e.this)?;
35535                        self.write(")");
35536                    }
35537                    6 => {
35538                        self.write_keyword("MAKE_TIMESTAMP");
35539                        self.write("(");
35540                        self.generate_expression(&e.this)?;
35541                        self.write(")");
35542                    }
35543                    _ => {
35544                        self.write_keyword("TO_TIMESTAMP");
35545                        self.write("(");
35546                        self.generate_expression(&e.this)?;
35547                        self.write(&format!(" / POWER(10, {}))", scale));
35548                        self.write_keyword(" AT TIME ZONE");
35549                        self.write(" 'UTC'");
35550                    }
35551                }
35552            }
35553            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35554                // Doris/StarRocks: FROM_UNIXTIME(value)
35555                self.write_keyword("FROM_UNIXTIME");
35556                self.write("(");
35557                self.generate_expression(&e.this)?;
35558                self.write(")");
35559            }
35560            Some(DialectType::Oracle) => {
35561                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35562                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35563                self.generate_expression(&e.this)?;
35564                self.write(" / 86400)");
35565            }
35566            Some(DialectType::Redshift) => {
35567                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35568                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35569                self.write("(TIMESTAMP 'epoch' + ");
35570                if scale == 0 {
35571                    self.generate_expression(&e.this)?;
35572                } else {
35573                    self.write("(");
35574                    self.generate_expression(&e.this)?;
35575                    self.write(&format!(" / POWER(10, {}))", scale));
35576                }
35577                self.write(" * INTERVAL '1 SECOND')");
35578            }
35579            _ => {
35580                // Default: TO_TIMESTAMP(value[, scale])
35581                self.write_keyword("TO_TIMESTAMP");
35582                self.write("(");
35583                self.generate_expression(&e.this)?;
35584                if let Some(s) = e.scale {
35585                    self.write(", ");
35586                    self.write(&s.to_string());
35587                }
35588                self.write(")");
35589            }
35590        }
35591        Ok(())
35592    }
35593
35594    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35595        // NAME col VALUE col1, col2, ...
35596        if !matches!(&*e.this, Expression::Null(_)) {
35597            self.write_keyword("NAME");
35598            self.write_space();
35599            self.generate_expression(&e.this)?;
35600        }
35601        if !e.expressions.is_empty() {
35602            self.write_space();
35603            self.write_keyword("VALUE");
35604            self.write_space();
35605            for (i, expr) in e.expressions.iter().enumerate() {
35606                if i > 0 {
35607                    self.write(", ");
35608                }
35609                self.generate_expression(expr)?;
35610            }
35611        }
35612        Ok(())
35613    }
35614
35615    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35616        // this(expressions) or (this)(expressions)
35617        if e.wrapped.is_some() {
35618            self.write("(");
35619        }
35620        self.generate_expression(&e.this)?;
35621        if e.wrapped.is_some() {
35622            self.write(")");
35623        }
35624        self.write("(");
35625        for (i, expr) in e.expressions.iter().enumerate() {
35626            if i > 0 {
35627                self.write(", ");
35628            }
35629            self.generate_expression(expr)?;
35630        }
35631        self.write(")");
35632        Ok(())
35633    }
35634
35635    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35636        // USING TEMPLATE this
35637        self.write_keyword("USING TEMPLATE");
35638        self.write_space();
35639        self.generate_expression(&e.this)?;
35640        Ok(())
35641    }
35642
35643    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35644        // UTC_TIME
35645        self.write_keyword("UTC_TIME");
35646        Ok(())
35647    }
35648
35649    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35650        // UTC_TIMESTAMP
35651        self.write_keyword("UTC_TIMESTAMP");
35652        Ok(())
35653    }
35654
35655    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35656        use crate::dialects::DialectType;
35657        // Choose UUID function name based on target dialect
35658        let func_name = match self.config.dialect {
35659            Some(DialectType::Snowflake) => "UUID_STRING",
35660            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35661            Some(DialectType::BigQuery) => "GENERATE_UUID",
35662            _ => {
35663                if let Some(name) = &e.name {
35664                    name.as_str()
35665                } else {
35666                    "UUID"
35667                }
35668            }
35669        };
35670        self.write_keyword(func_name);
35671        self.write("(");
35672        if let Some(this) = &e.this {
35673            self.generate_expression(this)?;
35674        }
35675        self.write(")");
35676        Ok(())
35677    }
35678
35679    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35680        // MAP(key1, value1, key2, value2, ...)
35681        self.write_keyword("MAP");
35682        self.write("(");
35683        let mut first = true;
35684        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35685            if !first {
35686                self.write(", ");
35687            }
35688            self.generate_expression(k)?;
35689            self.write(", ");
35690            self.generate_expression(v)?;
35691            first = false;
35692        }
35693        self.write(")");
35694        Ok(())
35695    }
35696
35697    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35698        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35699        self.write_keyword("VECTOR_SEARCH");
35700        self.write("(");
35701        self.generate_expression(&e.this)?;
35702        if let Some(col) = &e.column_to_search {
35703            self.write(", ");
35704            self.generate_expression(col)?;
35705        }
35706        if let Some(query_table) = &e.query_table {
35707            self.write(", ");
35708            self.generate_expression(query_table)?;
35709        }
35710        if let Some(query_col) = &e.query_column_to_search {
35711            self.write(", ");
35712            self.generate_expression(query_col)?;
35713        }
35714        if let Some(top_k) = &e.top_k {
35715            self.write(", ");
35716            self.generate_expression(top_k)?;
35717        }
35718        if let Some(dist_type) = &e.distance_type {
35719            self.write(", ");
35720            self.generate_expression(dist_type)?;
35721        }
35722        self.write(")");
35723        Ok(())
35724    }
35725
35726    fn generate_version(&mut self, e: &Version) -> Result<()> {
35727        // Python: f"FOR {expression.name} {kind} {expr}"
35728        // e.this = Identifier("TIMESTAMP" or "VERSION")
35729        // e.kind = "AS OF" (or "BETWEEN", etc.)
35730        // e.expression = the value expression
35731        // Hive does NOT use the FOR prefix for time travel
35732        use crate::dialects::DialectType;
35733        let skip_for = matches!(
35734            self.config.dialect,
35735            Some(DialectType::Hive) | Some(DialectType::Spark)
35736        );
35737        if !skip_for {
35738            self.write_keyword("FOR");
35739            self.write_space();
35740        }
35741        // Extract the name from this (which is an Identifier expression)
35742        match e.this.as_ref() {
35743            Expression::Identifier(ident) => {
35744                self.write_keyword(&ident.name);
35745            }
35746            _ => {
35747                self.generate_expression(&e.this)?;
35748            }
35749        }
35750        self.write_space();
35751        self.write_keyword(&e.kind);
35752        if let Some(expression) = &e.expression {
35753            self.write_space();
35754            self.generate_expression(expression)?;
35755        }
35756        Ok(())
35757    }
35758
35759    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35760        // Python: return self.sql(expression, "this")
35761        self.generate_expression(&e.this)?;
35762        Ok(())
35763    }
35764
35765    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35766        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35767        if e.this.is_some() {
35768            self.write_keyword("NOT VOLATILE");
35769        } else {
35770            self.write_keyword("VOLATILE");
35771        }
35772        Ok(())
35773    }
35774
35775    fn generate_watermark_column_constraint(
35776        &mut self,
35777        e: &WatermarkColumnConstraint,
35778    ) -> Result<()> {
35779        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35780        self.write_keyword("WATERMARK FOR");
35781        self.write_space();
35782        self.generate_expression(&e.this)?;
35783        self.write_space();
35784        self.write_keyword("AS");
35785        self.write_space();
35786        self.generate_expression(&e.expression)?;
35787        Ok(())
35788    }
35789
35790    fn generate_week(&mut self, e: &Week) -> Result<()> {
35791        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35792        self.write_keyword("WEEK");
35793        self.write("(");
35794        self.generate_expression(&e.this)?;
35795        if let Some(mode) = &e.mode {
35796            self.write(", ");
35797            self.generate_expression(mode)?;
35798        }
35799        self.write(")");
35800        Ok(())
35801    }
35802
35803    fn generate_when(&mut self, e: &When) -> Result<()> {
35804        // Python: WHEN {matched}{source}{condition} THEN {then}
35805        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35806        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35807        self.write_keyword("WHEN");
35808        self.write_space();
35809
35810        // Check if matched
35811        if let Some(matched) = &e.matched {
35812            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35813            match matched.as_ref() {
35814                Expression::Boolean(b) if b.value => {
35815                    self.write_keyword("MATCHED");
35816                }
35817                _ => {
35818                    self.write_keyword("NOT MATCHED");
35819                }
35820            }
35821        } else {
35822            self.write_keyword("NOT MATCHED");
35823        }
35824
35825        // BY SOURCE / BY TARGET
35826        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35827        // BY TARGET is the default and typically omitted in output
35828        // Only emit if the dialect supports BY SOURCE syntax
35829        if self.config.matched_by_source {
35830            if let Some(source) = &e.source {
35831                if let Expression::Boolean(b) = source.as_ref() {
35832                    if b.value {
35833                        // BY SOURCE
35834                        self.write_space();
35835                        self.write_keyword("BY SOURCE");
35836                    }
35837                    // BY TARGET (b.value == false) is omitted as it's the default
35838                } else {
35839                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35840                    self.write_space();
35841                    self.write_keyword("BY SOURCE");
35842                }
35843            }
35844        }
35845
35846        // Condition
35847        if let Some(condition) = &e.condition {
35848            self.write_space();
35849            self.write_keyword("AND");
35850            self.write_space();
35851            self.generate_expression(condition)?;
35852        }
35853
35854        self.write_space();
35855        self.write_keyword("THEN");
35856        self.write_space();
35857
35858        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35859        // MERGE actions are stored as Tuples with the action keyword as first element
35860        self.generate_merge_action(&e.then)?;
35861
35862        Ok(())
35863    }
35864
35865    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35866        match action {
35867            Expression::Tuple(tuple) => {
35868                let elements = &tuple.expressions;
35869                if elements.is_empty() {
35870                    return self.generate_expression(action);
35871                }
35872                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35873                match &elements[0] {
35874                    Expression::Var(v) if v.this == "INSERT" => {
35875                        self.write_keyword("INSERT");
35876                        // Spark: INSERT * (insert all columns)
35877                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35878                            self.write(" *");
35879                        } else {
35880                            let mut values_idx = 1;
35881                            // Check if second element is column list (Tuple)
35882                            if elements.len() > 1 {
35883                                if let Expression::Tuple(cols) = &elements[1] {
35884                                    // Could be columns or values - if there's a third element, second is columns
35885                                    if elements.len() > 2 {
35886                                        // Second is columns, third is values
35887                                        self.write(" (");
35888                                        for (i, col) in cols.expressions.iter().enumerate() {
35889                                            if i > 0 {
35890                                                self.write(", ");
35891                                            }
35892                                            // Strip MERGE target qualifiers from INSERT column list
35893                                            if !self.merge_strip_qualifiers.is_empty() {
35894                                                let stripped = self.strip_merge_qualifier(col);
35895                                                self.generate_expression(&stripped)?;
35896                                            } else {
35897                                                self.generate_expression(col)?;
35898                                            }
35899                                        }
35900                                        self.write(")");
35901                                        values_idx = 2;
35902                                    } else {
35903                                        // Only two elements: INSERT + values (no explicit columns)
35904                                        values_idx = 1;
35905                                    }
35906                                }
35907                            }
35908                            // Generate VALUES clause
35909                            if values_idx < elements.len() {
35910                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35911                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35912                                if !is_row {
35913                                    self.write_space();
35914                                    self.write_keyword("VALUES");
35915                                }
35916                                self.write(" ");
35917                                if let Expression::Tuple(vals) = &elements[values_idx] {
35918                                    self.write("(");
35919                                    for (i, val) in vals.expressions.iter().enumerate() {
35920                                        if i > 0 {
35921                                            self.write(", ");
35922                                        }
35923                                        self.generate_expression(val)?;
35924                                    }
35925                                    self.write(")");
35926                                } else {
35927                                    self.generate_expression(&elements[values_idx])?;
35928                                }
35929                            }
35930                        } // close else for INSERT * check
35931                    }
35932                    Expression::Var(v) if v.this == "UPDATE" => {
35933                        self.write_keyword("UPDATE");
35934                        // Spark: UPDATE * (update all columns)
35935                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35936                            self.write(" *");
35937                        } else if elements.len() > 1 {
35938                            self.write_space();
35939                            self.write_keyword("SET");
35940                            // In pretty mode, put assignments on next line with extra indent
35941                            if self.config.pretty {
35942                                self.write_newline();
35943                                self.indent_level += 1;
35944                                self.write_indent();
35945                            } else {
35946                                self.write_space();
35947                            }
35948                            if let Expression::Tuple(assignments) = &elements[1] {
35949                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35950                                    if i > 0 {
35951                                        if self.config.pretty {
35952                                            self.write(",");
35953                                            self.write_newline();
35954                                            self.write_indent();
35955                                        } else {
35956                                            self.write(", ");
35957                                        }
35958                                    }
35959                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35960                                    if !self.merge_strip_qualifiers.is_empty() {
35961                                        self.generate_merge_set_assignment(assignment)?;
35962                                    } else {
35963                                        self.generate_expression(assignment)?;
35964                                    }
35965                                }
35966                            } else {
35967                                self.generate_expression(&elements[1])?;
35968                            }
35969                            if self.config.pretty {
35970                                self.indent_level -= 1;
35971                            }
35972                        }
35973                    }
35974                    _ => {
35975                        // Fallback: generic tuple generation
35976                        self.generate_expression(action)?;
35977                    }
35978                }
35979            }
35980            Expression::Var(v)
35981                if v.this == "INSERT"
35982                    || v.this == "UPDATE"
35983                    || v.this == "DELETE"
35984                    || v.this == "DO NOTHING" =>
35985            {
35986                self.write_keyword(&v.this);
35987            }
35988            _ => {
35989                self.generate_expression(action)?;
35990            }
35991        }
35992        Ok(())
35993    }
35994
35995    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35996    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35997        match assignment {
35998            Expression::Eq(eq) => {
35999                // Strip qualifier from the left side if it matches a MERGE target name
36000                let stripped_left = self.strip_merge_qualifier(&eq.left);
36001                self.generate_expression(&stripped_left)?;
36002                self.write(" = ");
36003                self.generate_expression(&eq.right)?;
36004                Ok(())
36005            }
36006            other => self.generate_expression(other),
36007        }
36008    }
36009
36010    /// Strip table qualifier from a column reference if it matches a MERGE target name
36011    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
36012        match expr {
36013            Expression::Column(col) => {
36014                if let Some(ref table_ident) = col.table {
36015                    if self
36016                        .merge_strip_qualifiers
36017                        .iter()
36018                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
36019                    {
36020                        // Strip the table qualifier
36021                        let mut col = col.clone();
36022                        col.table = None;
36023                        return Expression::Column(col);
36024                    }
36025                }
36026                expr.clone()
36027            }
36028            Expression::Dot(dot) => {
36029                // table.column -> column (strip qualifier)
36030                if let Expression::Identifier(id) = &dot.this {
36031                    if self
36032                        .merge_strip_qualifiers
36033                        .iter()
36034                        .any(|n| n.eq_ignore_ascii_case(&id.name))
36035                    {
36036                        return Expression::Identifier(dot.field.clone());
36037                    }
36038                }
36039                expr.clone()
36040            }
36041            _ => expr.clone(),
36042        }
36043    }
36044
36045    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
36046        // Python: return self.expressions(expression, sep=" ", indent=False)
36047        for (i, expr) in e.expressions.iter().enumerate() {
36048            if i > 0 {
36049                // In pretty mode, each WHEN clause on its own line
36050                if self.config.pretty {
36051                    self.write_newline();
36052                    self.write_indent();
36053                } else {
36054                    self.write_space();
36055                }
36056            }
36057            self.generate_expression(expr)?;
36058        }
36059        Ok(())
36060    }
36061
36062    fn generate_where(&mut self, e: &Where) -> Result<()> {
36063        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
36064        self.write_keyword("WHERE");
36065        self.write_space();
36066        self.generate_expression(&e.this)?;
36067        Ok(())
36068    }
36069
36070    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
36071        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
36072        self.write_keyword("WIDTH_BUCKET");
36073        self.write("(");
36074        self.generate_expression(&e.this)?;
36075        if let Some(min_value) = &e.min_value {
36076            self.write(", ");
36077            self.generate_expression(min_value)?;
36078        }
36079        if let Some(max_value) = &e.max_value {
36080            self.write(", ");
36081            self.generate_expression(max_value)?;
36082        }
36083        if let Some(num_buckets) = &e.num_buckets {
36084            self.write(", ");
36085            self.generate_expression(num_buckets)?;
36086        }
36087        self.write(")");
36088        Ok(())
36089    }
36090
36091    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
36092        // Window specification: PARTITION BY ... ORDER BY ... frame
36093        self.generate_window_spec(e)
36094    }
36095
36096    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
36097        // Window specification: PARTITION BY ... ORDER BY ... frame
36098        let mut has_content = false;
36099
36100        // PARTITION BY
36101        if !e.partition_by.is_empty() {
36102            self.write_keyword("PARTITION BY");
36103            self.write_space();
36104            for (i, expr) in e.partition_by.iter().enumerate() {
36105                if i > 0 {
36106                    self.write(", ");
36107                }
36108                self.generate_expression(expr)?;
36109            }
36110            has_content = true;
36111        }
36112
36113        // ORDER BY
36114        if !e.order_by.is_empty() {
36115            if has_content {
36116                self.write_space();
36117            }
36118            self.write_keyword("ORDER BY");
36119            self.write_space();
36120            for (i, ordered) in e.order_by.iter().enumerate() {
36121                if i > 0 {
36122                    self.write(", ");
36123                }
36124                self.generate_expression(&ordered.this)?;
36125                if ordered.desc {
36126                    self.write_space();
36127                    self.write_keyword("DESC");
36128                } else if ordered.explicit_asc {
36129                    self.write_space();
36130                    self.write_keyword("ASC");
36131                }
36132                if let Some(nulls_first) = ordered.nulls_first {
36133                    self.write_space();
36134                    self.write_keyword("NULLS");
36135                    self.write_space();
36136                    if nulls_first {
36137                        self.write_keyword("FIRST");
36138                    } else {
36139                        self.write_keyword("LAST");
36140                    }
36141                }
36142            }
36143            has_content = true;
36144        }
36145
36146        // Frame specification
36147        if let Some(frame) = &e.frame {
36148            if has_content {
36149                self.write_space();
36150            }
36151            self.generate_window_frame(frame)?;
36152        }
36153
36154        Ok(())
36155    }
36156
36157    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
36158        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
36159        self.write_keyword("WITH");
36160        self.write_space();
36161        if e.no.is_some() {
36162            self.write_keyword("NO");
36163            self.write_space();
36164        }
36165        self.write_keyword("DATA");
36166
36167        // statistics
36168        if let Some(statistics) = &e.statistics {
36169            self.write_space();
36170            self.write_keyword("AND");
36171            self.write_space();
36172            // Check if statistics is true or false
36173            match statistics.as_ref() {
36174                Expression::Boolean(b) if !b.value => {
36175                    self.write_keyword("NO");
36176                    self.write_space();
36177                }
36178                _ => {}
36179            }
36180            self.write_keyword("STATISTICS");
36181        }
36182        Ok(())
36183    }
36184
36185    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
36186        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
36187        self.write_keyword("WITH FILL");
36188
36189        if let Some(from_) = &e.from_ {
36190            self.write_space();
36191            self.write_keyword("FROM");
36192            self.write_space();
36193            self.generate_expression(from_)?;
36194        }
36195
36196        if let Some(to) = &e.to {
36197            self.write_space();
36198            self.write_keyword("TO");
36199            self.write_space();
36200            self.generate_expression(to)?;
36201        }
36202
36203        if let Some(step) = &e.step {
36204            self.write_space();
36205            self.write_keyword("STEP");
36206            self.write_space();
36207            self.generate_expression(step)?;
36208        }
36209
36210        if let Some(staleness) = &e.staleness {
36211            self.write_space();
36212            self.write_keyword("STALENESS");
36213            self.write_space();
36214            self.generate_expression(staleness)?;
36215        }
36216
36217        if let Some(interpolate) = &e.interpolate {
36218            self.write_space();
36219            self.write_keyword("INTERPOLATE");
36220            self.write(" (");
36221            // INTERPOLATE items use reversed alias format: name AS expression
36222            self.generate_interpolate_item(interpolate)?;
36223            self.write(")");
36224        }
36225
36226        Ok(())
36227    }
36228
36229    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
36230    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
36231        match expr {
36232            Expression::Alias(alias) => {
36233                // Output as: alias_name AS expression
36234                self.generate_identifier(&alias.alias)?;
36235                self.write_space();
36236                self.write_keyword("AS");
36237                self.write_space();
36238                self.generate_expression(&alias.this)?;
36239            }
36240            Expression::Tuple(tuple) => {
36241                for (i, item) in tuple.expressions.iter().enumerate() {
36242                    if i > 0 {
36243                        self.write(", ");
36244                    }
36245                    self.generate_interpolate_item(item)?;
36246                }
36247            }
36248            other => {
36249                self.generate_expression(other)?;
36250            }
36251        }
36252        Ok(())
36253    }
36254
36255    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
36256        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
36257        self.write_keyword("WITH JOURNAL TABLE");
36258        self.write("=");
36259        self.generate_expression(&e.this)?;
36260        Ok(())
36261    }
36262
36263    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
36264        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
36265        self.generate_expression(&e.this)?;
36266        self.write_space();
36267        self.write_keyword("WITH");
36268        self.write_space();
36269        self.write_keyword(&e.op);
36270        Ok(())
36271    }
36272
36273    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
36274        // Python: return f"WITH {self.expressions(expression, flat=True)}"
36275        self.write_keyword("WITH");
36276        self.write_space();
36277        for (i, expr) in e.expressions.iter().enumerate() {
36278            if i > 0 {
36279                self.write(", ");
36280            }
36281            self.generate_expression(expr)?;
36282        }
36283        Ok(())
36284    }
36285
36286    fn generate_with_schema_binding_property(
36287        &mut self,
36288        e: &WithSchemaBindingProperty,
36289    ) -> Result<()> {
36290        // Python: return f"WITH {self.sql(expression, 'this')}"
36291        self.write_keyword("WITH");
36292        self.write_space();
36293        self.generate_expression(&e.this)?;
36294        Ok(())
36295    }
36296
36297    fn generate_with_system_versioning_property(
36298        &mut self,
36299        e: &WithSystemVersioningProperty,
36300    ) -> Result<()> {
36301        // Python: complex logic for SYSTEM_VERSIONING with options
36302        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36303        // or SYSTEM_VERSIONING=ON/OFF
36304        // with WITH(...) wrapper if with_ is set
36305
36306        let mut parts = Vec::new();
36307
36308        if let Some(this) = &e.this {
36309            // HISTORY_TABLE=...
36310            let mut s = String::from("HISTORY_TABLE=");
36311            let mut gen = Generator::new();
36312            gen.generate_expression(this)?;
36313            s.push_str(&gen.output);
36314            parts.push(s);
36315        }
36316
36317        if let Some(data_consistency) = &e.data_consistency {
36318            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36319            let mut gen = Generator::new();
36320            gen.generate_expression(data_consistency)?;
36321            s.push_str(&gen.output);
36322            parts.push(s);
36323        }
36324
36325        if let Some(retention_period) = &e.retention_period {
36326            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36327            let mut gen = Generator::new();
36328            gen.generate_expression(retention_period)?;
36329            s.push_str(&gen.output);
36330            parts.push(s);
36331        }
36332
36333        self.write_keyword("SYSTEM_VERSIONING");
36334        self.write("=");
36335
36336        if !parts.is_empty() {
36337            self.write_keyword("ON");
36338            self.write("(");
36339            self.write(&parts.join(", "));
36340            self.write(")");
36341        } else if e.on.is_some() {
36342            self.write_keyword("ON");
36343        } else {
36344            self.write_keyword("OFF");
36345        }
36346
36347        // Wrap in WITH(...) if with_ is set
36348        if e.with_.is_some() {
36349            let inner = self.output.clone();
36350            self.output.clear();
36351            self.write("WITH(");
36352            self.write(&inner);
36353            self.write(")");
36354        }
36355
36356        Ok(())
36357    }
36358
36359    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36360        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36361        self.write_keyword("WITH");
36362        self.write(" (");
36363        for (i, expr) in e.expressions.iter().enumerate() {
36364            if i > 0 {
36365                self.write(", ");
36366            }
36367            self.generate_expression(expr)?;
36368        }
36369        self.write(")");
36370        Ok(())
36371    }
36372
36373    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36374        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36375        // return self.func("XMLELEMENT", name, *expression.expressions)
36376        self.write_keyword("XMLELEMENT");
36377        self.write("(");
36378
36379        if e.evalname.is_some() {
36380            self.write_keyword("EVALNAME");
36381        } else {
36382            self.write_keyword("NAME");
36383        }
36384        self.write_space();
36385        self.generate_expression(&e.this)?;
36386
36387        for expr in &e.expressions {
36388            self.write(", ");
36389            self.generate_expression(expr)?;
36390        }
36391        self.write(")");
36392        Ok(())
36393    }
36394
36395    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36396        // XMLGET(this, expression [, instance])
36397        self.write_keyword("XMLGET");
36398        self.write("(");
36399        self.generate_expression(&e.this)?;
36400        self.write(", ");
36401        self.generate_expression(&e.expression)?;
36402        if let Some(instance) = &e.instance {
36403            self.write(", ");
36404            self.generate_expression(instance)?;
36405        }
36406        self.write(")");
36407        Ok(())
36408    }
36409
36410    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36411        // Python: this + optional (expr)
36412        self.generate_expression(&e.this)?;
36413        if let Some(expression) = &e.expression {
36414            self.write("(");
36415            self.generate_expression(expression)?;
36416            self.write(")");
36417        }
36418        Ok(())
36419    }
36420
36421    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36422        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36423        self.write_keyword("XMLTABLE");
36424        self.write("(");
36425
36426        if self.config.pretty {
36427            self.indent_level += 1;
36428            self.write_newline();
36429            self.write_indent();
36430            self.generate_expression(&e.this)?;
36431
36432            if let Some(passing) = &e.passing {
36433                self.write_newline();
36434                self.write_indent();
36435                self.write_keyword("PASSING");
36436                if let Expression::Tuple(tuple) = passing.as_ref() {
36437                    for expr in &tuple.expressions {
36438                        self.write_newline();
36439                        self.indent_level += 1;
36440                        self.write_indent();
36441                        self.generate_expression(expr)?;
36442                        self.indent_level -= 1;
36443                    }
36444                } else {
36445                    self.write_newline();
36446                    self.indent_level += 1;
36447                    self.write_indent();
36448                    self.generate_expression(passing)?;
36449                    self.indent_level -= 1;
36450                }
36451            }
36452
36453            if e.by_ref.is_some() {
36454                self.write_newline();
36455                self.write_indent();
36456                self.write_keyword("RETURNING SEQUENCE BY REF");
36457            }
36458
36459            if !e.columns.is_empty() {
36460                self.write_newline();
36461                self.write_indent();
36462                self.write_keyword("COLUMNS");
36463                for (i, col) in e.columns.iter().enumerate() {
36464                    self.write_newline();
36465                    self.indent_level += 1;
36466                    self.write_indent();
36467                    self.generate_expression(col)?;
36468                    self.indent_level -= 1;
36469                    if i < e.columns.len() - 1 {
36470                        self.write(",");
36471                    }
36472                }
36473            }
36474
36475            self.indent_level -= 1;
36476            self.write_newline();
36477            self.write_indent();
36478            self.write(")");
36479            return Ok(());
36480        }
36481
36482        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36483        if let Some(namespaces) = &e.namespaces {
36484            self.write_keyword("XMLNAMESPACES");
36485            self.write("(");
36486            // Unwrap Tuple if present to avoid extra parentheses
36487            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36488                for (i, expr) in tuple.expressions.iter().enumerate() {
36489                    if i > 0 {
36490                        self.write(", ");
36491                    }
36492                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36493                    // See xmlnamespace_sql in generator.py
36494                    if !matches!(expr, Expression::Alias(_)) {
36495                        self.write_keyword("DEFAULT");
36496                        self.write_space();
36497                    }
36498                    self.generate_expression(expr)?;
36499                }
36500            } else {
36501                // Single namespace - check if DEFAULT
36502                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36503                    self.write_keyword("DEFAULT");
36504                    self.write_space();
36505                }
36506                self.generate_expression(namespaces)?;
36507            }
36508            self.write("), ");
36509        }
36510
36511        // XPath expression
36512        self.generate_expression(&e.this)?;
36513
36514        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36515        if let Some(passing) = &e.passing {
36516            self.write_space();
36517            self.write_keyword("PASSING");
36518            self.write_space();
36519            // Unwrap Tuple if present to avoid extra parentheses
36520            if let Expression::Tuple(tuple) = passing.as_ref() {
36521                for (i, expr) in tuple.expressions.iter().enumerate() {
36522                    if i > 0 {
36523                        self.write(", ");
36524                    }
36525                    self.generate_expression(expr)?;
36526                }
36527            } else {
36528                self.generate_expression(passing)?;
36529            }
36530        }
36531
36532        // RETURNING SEQUENCE BY REF
36533        if e.by_ref.is_some() {
36534            self.write_space();
36535            self.write_keyword("RETURNING SEQUENCE BY REF");
36536        }
36537
36538        // COLUMNS clause
36539        if !e.columns.is_empty() {
36540            self.write_space();
36541            self.write_keyword("COLUMNS");
36542            self.write_space();
36543            for (i, col) in e.columns.iter().enumerate() {
36544                if i > 0 {
36545                    self.write(", ");
36546                }
36547                self.generate_expression(col)?;
36548            }
36549        }
36550
36551        self.write(")");
36552        Ok(())
36553    }
36554
36555    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36556        // Python: return self.connector_sql(expression, "XOR", stack)
36557        // Handles: this XOR expression or expressions joined by XOR
36558        if let Some(this) = &e.this {
36559            self.generate_expression(this)?;
36560            if let Some(expression) = &e.expression {
36561                self.write_space();
36562                self.write_keyword("XOR");
36563                self.write_space();
36564                self.generate_expression(expression)?;
36565            }
36566        }
36567
36568        // Handle multiple expressions
36569        for (i, expr) in e.expressions.iter().enumerate() {
36570            if i > 0 || e.this.is_some() {
36571                self.write_space();
36572                self.write_keyword("XOR");
36573                self.write_space();
36574            }
36575            self.generate_expression(expr)?;
36576        }
36577        Ok(())
36578    }
36579
36580    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36581        // ZIPF(this, elementcount [, gen])
36582        self.write_keyword("ZIPF");
36583        self.write("(");
36584        self.generate_expression(&e.this)?;
36585        if let Some(elementcount) = &e.elementcount {
36586            self.write(", ");
36587            self.generate_expression(elementcount)?;
36588        }
36589        if let Some(gen) = &e.gen {
36590            self.write(", ");
36591            self.generate_expression(gen)?;
36592        }
36593        self.write(")");
36594        Ok(())
36595    }
36596}
36597
36598impl Default for Generator {
36599    fn default() -> Self {
36600        Self::new()
36601    }
36602}
36603
36604#[cfg(test)]
36605mod tests {
36606    use super::*;
36607    use crate::parser::Parser;
36608
36609    fn roundtrip(sql: &str) -> String {
36610        let ast = Parser::parse_sql(sql).unwrap();
36611        Generator::sql(&ast[0]).unwrap()
36612    }
36613
36614    #[test]
36615    fn test_simple_select() {
36616        let result = roundtrip("SELECT 1");
36617        assert_eq!(result, "SELECT 1");
36618    }
36619
36620    #[test]
36621    fn test_select_from() {
36622        let result = roundtrip("SELECT a, b FROM t");
36623        assert_eq!(result, "SELECT a, b FROM t");
36624    }
36625
36626    #[test]
36627    fn test_select_where() {
36628        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36629        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36630    }
36631
36632    #[test]
36633    fn test_select_join() {
36634        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36635        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36636    }
36637
36638    #[test]
36639    fn test_insert() {
36640        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36641        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36642    }
36643
36644    #[test]
36645    fn test_pretty_print() {
36646        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36647        let result = Generator::pretty_sql(&ast[0]).unwrap();
36648        assert!(result.contains('\n'));
36649    }
36650
36651    #[test]
36652    fn test_window_function() {
36653        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36654        assert_eq!(
36655            result,
36656            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36657        );
36658    }
36659
36660    #[test]
36661    fn test_window_function_with_frame() {
36662        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36663        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36664    }
36665
36666    #[test]
36667    fn test_aggregate_with_filter() {
36668        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36669        assert_eq!(
36670            result,
36671            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36672        );
36673    }
36674
36675    #[test]
36676    fn test_subscript() {
36677        let result = roundtrip("SELECT arr[0]");
36678        assert_eq!(result, "SELECT arr[0]");
36679    }
36680
36681    // DDL tests
36682    #[test]
36683    fn test_create_table() {
36684        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36685        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36686    }
36687
36688    #[test]
36689    fn test_create_table_with_constraints() {
36690        let result = roundtrip(
36691            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36692        );
36693        assert_eq!(
36694            result,
36695            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36696        );
36697    }
36698
36699    #[test]
36700    fn test_create_table_if_not_exists() {
36701        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36702        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36703    }
36704
36705    #[test]
36706    fn test_drop_table() {
36707        let result = roundtrip("DROP TABLE users");
36708        assert_eq!(result, "DROP TABLE users");
36709    }
36710
36711    #[test]
36712    fn test_drop_table_if_exists_cascade() {
36713        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36714        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36715    }
36716
36717    #[test]
36718    fn test_alter_table_add_column() {
36719        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36720        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36721    }
36722
36723    #[test]
36724    fn test_alter_table_drop_column() {
36725        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36726        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36727    }
36728
36729    #[test]
36730    fn test_create_index() {
36731        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36732        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36733    }
36734
36735    #[test]
36736    fn test_create_unique_index() {
36737        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36738        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36739    }
36740
36741    #[test]
36742    fn test_drop_index() {
36743        let result = roundtrip("DROP INDEX idx_name");
36744        assert_eq!(result, "DROP INDEX idx_name");
36745    }
36746
36747    #[test]
36748    fn test_create_view() {
36749        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36750        assert_eq!(
36751            result,
36752            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36753        );
36754    }
36755
36756    #[test]
36757    fn test_drop_view() {
36758        let result = roundtrip("DROP VIEW active_users");
36759        assert_eq!(result, "DROP VIEW active_users");
36760    }
36761
36762    #[test]
36763    fn test_truncate() {
36764        let result = roundtrip("TRUNCATE TABLE users");
36765        assert_eq!(result, "TRUNCATE TABLE users");
36766    }
36767
36768    #[test]
36769    fn test_string_literal_escaping_default() {
36770        // Default: double single quotes
36771        let result = roundtrip("SELECT 'hello'");
36772        assert_eq!(result, "SELECT 'hello'");
36773
36774        // Single quotes are doubled
36775        let result = roundtrip("SELECT 'it''s a test'");
36776        assert_eq!(result, "SELECT 'it''s a test'");
36777    }
36778
36779    #[test]
36780    fn test_not_in_style_prefix_default_generic() {
36781        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36782        assert_eq!(
36783            result,
36784            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36785        );
36786    }
36787
36788    #[test]
36789    fn test_not_in_style_infix_generic_override() {
36790        let ast =
36791            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36792                .unwrap();
36793        let config = GeneratorConfig {
36794            not_in_style: NotInStyle::Infix,
36795            ..Default::default()
36796        };
36797        let mut gen = Generator::with_config(config);
36798        let result = gen.generate(&ast[0]).unwrap();
36799        assert_eq!(
36800            result,
36801            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36802        );
36803    }
36804
36805    #[test]
36806    fn test_string_literal_escaping_mysql() {
36807        use crate::dialects::DialectType;
36808
36809        let config = GeneratorConfig {
36810            dialect: Some(DialectType::MySQL),
36811            ..Default::default()
36812        };
36813
36814        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36815        let mut gen = Generator::with_config(config.clone());
36816        let result = gen.generate(&ast[0]).unwrap();
36817        assert_eq!(result, "SELECT 'hello'");
36818
36819        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36820        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36821        let mut gen = Generator::with_config(config.clone());
36822        let result = gen.generate(&ast[0]).unwrap();
36823        assert_eq!(result, "SELECT 'it''s'");
36824    }
36825
36826    #[test]
36827    fn test_string_literal_escaping_postgres() {
36828        use crate::dialects::DialectType;
36829
36830        let config = GeneratorConfig {
36831            dialect: Some(DialectType::PostgreSQL),
36832            ..Default::default()
36833        };
36834
36835        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36836        let mut gen = Generator::with_config(config.clone());
36837        let result = gen.generate(&ast[0]).unwrap();
36838        assert_eq!(result, "SELECT 'hello'");
36839
36840        // PostgreSQL uses doubled quotes for regular strings
36841        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36842        let mut gen = Generator::with_config(config.clone());
36843        let result = gen.generate(&ast[0]).unwrap();
36844        assert_eq!(result, "SELECT 'it''s'");
36845    }
36846
36847    #[test]
36848    fn test_string_literal_escaping_bigquery() {
36849        use crate::dialects::DialectType;
36850
36851        let config = GeneratorConfig {
36852            dialect: Some(DialectType::BigQuery),
36853            ..Default::default()
36854        };
36855
36856        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36857        let mut gen = Generator::with_config(config.clone());
36858        let result = gen.generate(&ast[0]).unwrap();
36859        assert_eq!(result, "SELECT 'hello'");
36860
36861        // BigQuery escapes single quotes with backslash
36862        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36863        let mut gen = Generator::with_config(config.clone());
36864        let result = gen.generate(&ast[0]).unwrap();
36865        assert_eq!(result, "SELECT 'it\\'s'");
36866    }
36867
36868    #[test]
36869    fn test_generate_deep_and_chain_without_stack_growth() {
36870        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36871            Expression::column("c0"),
36872            Expression::number(0),
36873        )));
36874
36875        for i in 1..2500 {
36876            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36877                Expression::column(format!("c{i}")),
36878                Expression::number(i as i64),
36879            )));
36880            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
36881        }
36882
36883        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
36884        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36885    }
36886
36887    #[test]
36888    fn test_generate_deep_or_chain_without_stack_growth() {
36889        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36890            Expression::column("c0"),
36891            Expression::number(0),
36892        )));
36893
36894        for i in 1..2500 {
36895            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36896                Expression::column(format!("c{i}")),
36897                Expression::number(i as i64),
36898            )));
36899            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
36900        }
36901
36902        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
36903        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36904    }
36905}