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 std::borrow::Cow;
14use std::sync::Arc;
15
16use crate::error::Result;
17use crate::expressions::*;
18use crate::DialectType;
19
20/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
21///
22/// The generator walks the expression tree and emits dialect-specific SQL text.
23/// It supports pretty-printing with configurable indentation, identifier quoting,
24/// keyword casing, function name normalization, and 30+ SQL dialect variants.
25///
26/// # Usage
27///
28/// ```rust,ignore
29/// use polyglot_sql::generator::Generator;
30/// use polyglot_sql::parser::Parser;
31///
32/// let ast = Parser::parse_sql("SELECT 1")?;
33/// // Quick one-shot generation (default config):
34/// let sql = Generator::sql(&ast[0])?;
35///
36/// // Pretty-printed output:
37/// let pretty = Generator::pretty_sql(&ast[0])?;
38///
39/// // Custom config (e.g. for a specific dialect):
40/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
41/// let mut gen = Generator::with_config(config);
42/// let sql = gen.generate(&ast[0])?;
43/// ```
44pub struct Generator {
45    config: Arc<GeneratorConfig>,
46    output: String,
47    unsupported_messages: Vec<String>,
48    indent_level: usize,
49    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
50    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
51    athena_hive_context: bool,
52    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
53    sqlite_inline_pk_columns: std::collections::HashSet<String>,
54    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
55    merge_strip_qualifiers: Vec<String>,
56    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
57    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
58    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
59    /// Negative values indicate map key context (should NOT be wrapped).
60    clickhouse_nullable_depth: i32,
61}
62
63/// Controls how SQL function names are cased in generated output.
64///
65/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
66/// - `Lower` -- `count`, `sum`, `coalesce`
67/// - `None` -- preserve the original casing from the parsed input
68#[derive(Debug, Clone, Copy, PartialEq, Default)]
69pub enum NormalizeFunctions {
70    /// Emit function names in UPPER CASE (default).
71    #[default]
72    Upper,
73    /// Emit function names in lower case.
74    Lower,
75    /// Preserve the original casing from the parsed input.
76    None,
77}
78
79/// Strategy for generating row-limiting clauses across SQL dialects.
80#[derive(Debug, Clone, Copy, PartialEq, Default)]
81pub enum LimitFetchStyle {
82    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
83    #[default]
84    Limit,
85    /// `TOP n` -- TSQL (SQL Server).
86    Top,
87    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
88    FetchFirst,
89}
90
91/// Strategy for rendering negated IN predicates.
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum NotInStyle {
94    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
95    #[default]
96    Prefix,
97    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
98    Infix,
99}
100
101/// Controls how the generator reacts when it encounters unsupported output.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub enum UnsupportedLevel {
104    /// Ignore unsupported diagnostics and continue generation.
105    Ignore,
106    /// Collect unsupported diagnostics and continue generation.
107    #[default]
108    Warn,
109    /// Collect unsupported diagnostics and raise after generation completes.
110    Raise,
111    /// Raise immediately when the first unsupported feature is encountered.
112    Immediate,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116enum ConnectorOperator {
117    And,
118    Or,
119}
120
121impl ConnectorOperator {
122    fn keyword(self) -> &'static str {
123        match self {
124            Self::And => "AND",
125            Self::Or => "OR",
126        }
127    }
128}
129
130/// Identifier quote style (start/end characters)
131#[derive(Debug, Clone, Copy, PartialEq)]
132pub struct IdentifierQuoteStyle {
133    /// Start character for quoting identifiers (e.g., '"', '`', '[')
134    pub start: char,
135    /// End character for quoting identifiers (e.g., '"', '`', ']')
136    pub end: char,
137}
138
139impl Default for IdentifierQuoteStyle {
140    fn default() -> Self {
141        Self {
142            start: '"',
143            end: '"',
144        }
145    }
146}
147
148impl IdentifierQuoteStyle {
149    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
150    pub const DOUBLE_QUOTE: Self = Self {
151        start: '"',
152        end: '"',
153    };
154    /// Backtick style (MySQL, BigQuery, Spark, Hive)
155    pub const BACKTICK: Self = Self {
156        start: '`',
157        end: '`',
158    };
159    /// Square bracket style (TSQL, SQLite)
160    pub const BRACKET: Self = Self {
161        start: '[',
162        end: ']',
163    };
164}
165
166/// Configuration for the SQL [`Generator`].
167///
168/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
169/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
170/// syntax, feature support flags, and more.
171///
172/// Most users should start from `GeneratorConfig::default()` and override only the
173/// fields they need. Dialect-specific presets are applied automatically when
174/// `dialect` is set via the higher-level transpilation API.
175///
176/// # Key fields
177///
178/// | Field | Default | Purpose |
179/// |-------|---------|---------|
180/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
181/// | `pretty` | `false` | Enable multi-line, indented output |
182/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
183/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
184/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
185/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
186/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
187#[derive(Debug, Clone)]
188pub struct GeneratorConfig {
189    // ===== Basic formatting =====
190    /// Pretty print with indentation
191    pub pretty: bool,
192    /// Indentation string (default 2 spaces)
193    pub indent: &'static str,
194    /// Maximum text width before wrapping (default 80)
195    pub max_text_width: usize,
196    /// Quote identifier style (deprecated, use identifier_quote_style instead)
197    pub identifier_quote: char,
198    /// Identifier quote style with separate start/end characters
199    pub identifier_quote_style: IdentifierQuoteStyle,
200    /// Uppercase keywords
201    pub uppercase_keywords: bool,
202    /// Normalize identifiers to lowercase when generating
203    pub normalize_identifiers: bool,
204    /// Dialect type for dialect-specific generation
205    pub dialect: Option<crate::dialects::DialectType>,
206    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
207    pub source_dialect: Option<crate::dialects::DialectType>,
208    /// How unsupported generation should be handled.
209    pub unsupported_level: UnsupportedLevel,
210    /// Maximum number of unsupported diagnostics to include in raised errors.
211    pub max_unsupported: usize,
212    /// How to output function names (UPPER, lower, or as-is)
213    pub normalize_functions: NormalizeFunctions,
214    /// String escape character
215    pub string_escape: char,
216    /// Whether identifiers are case-sensitive
217    pub case_sensitive_identifiers: bool,
218    /// Whether unquoted identifiers can start with a digit
219    pub identifiers_can_start_with_digit: bool,
220    /// Whether to always quote identifiers regardless of reserved keyword status
221    /// Used by dialects like Athena/Presto that prefer quoted identifiers
222    pub always_quote_identifiers: bool,
223    /// How to render negated IN predicates in generic output.
224    pub not_in_style: NotInStyle,
225
226    // ===== Null handling =====
227    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
228    /// True: Full Support, false: No support
229    pub null_ordering_supported: bool,
230    /// Whether ignore nulls is inside the agg or outside
231    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
232    pub ignore_nulls_in_func: bool,
233    /// Whether the NVL2 function is supported
234    pub nvl2_supported: bool,
235
236    // ===== Limit/Fetch =====
237    /// How to output LIMIT clauses
238    pub limit_fetch_style: LimitFetchStyle,
239    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
240    pub limit_is_top: bool,
241    /// Whether limit and fetch allows expressions or just literals
242    pub limit_only_literals: bool,
243
244    // ===== Interval =====
245    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
246    pub single_string_interval: bool,
247    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
248    pub interval_allows_plural_form: bool,
249
250    // ===== CTE =====
251    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
252    pub cte_recursive_keyword_required: bool,
253
254    // ===== VALUES =====
255    /// Whether VALUES can be used as a table source
256    pub values_as_table: bool,
257    /// Wrap derived values in parens (standard but Spark doesn't support)
258    pub wrap_derived_values: bool,
259
260    // ===== TABLESAMPLE =====
261    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
262    pub tablesample_seed_keyword: &'static str,
263    /// Whether parentheses are required around the table sample's expression
264    pub tablesample_requires_parens: bool,
265    /// Whether a table sample clause's size needs to be followed by ROWS keyword
266    pub tablesample_size_is_rows: bool,
267    /// The keyword(s) to use when generating a sample clause
268    pub tablesample_keywords: &'static str,
269    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
270    pub tablesample_with_method: bool,
271    /// Whether the table alias comes after tablesample (Oracle, Hive)
272    pub alias_post_tablesample: bool,
273
274    // ===== Aggregate =====
275    /// Whether aggregate FILTER (WHERE ...) is supported
276    pub aggregate_filter_supported: bool,
277    /// Whether DISTINCT can be followed by multiple args in an AggFunc
278    pub multi_arg_distinct: bool,
279    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
280    pub quantified_no_paren_space: bool,
281    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
282    pub supports_median: bool,
283
284    // ===== SELECT =====
285    /// Whether SELECT ... INTO is supported
286    pub supports_select_into: bool,
287    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
288    pub locking_reads_supported: bool,
289
290    // ===== Table/Join =====
291    /// Whether a table is allowed to be renamed with a db
292    pub rename_table_with_db: bool,
293    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
294    pub semi_anti_join_with_side: bool,
295    /// Whether named columns are allowed in table aliases
296    pub supports_table_alias_columns: bool,
297    /// Whether join hints should be generated
298    pub join_hints: bool,
299    /// Whether table hints should be generated
300    pub table_hints: bool,
301    /// Whether query hints should be generated
302    pub query_hints: bool,
303    /// What kind of separator to use for query hints
304    pub query_hint_sep: &'static str,
305    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
306    pub supports_column_join_marks: bool,
307
308    // ===== DDL =====
309    /// Whether CREATE INDEX USING method should have no space before column parens
310    /// true: `USING btree(col)`, false: `USING btree (col)`
311    pub index_using_no_space: bool,
312    /// Whether UNLOGGED tables can be created
313    pub supports_unlogged_tables: bool,
314    /// Whether CREATE TABLE LIKE statement is supported
315    pub supports_create_table_like: bool,
316    /// Whether the LikeProperty needs to be inside the schema clause
317    pub like_property_inside_schema: bool,
318    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
319    pub alter_table_include_column_keyword: bool,
320    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
321    pub supports_table_copy: bool,
322    /// The syntax to use when altering the type of a column
323    pub alter_set_type: &'static str,
324    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
325    pub alter_set_wrapped: bool,
326
327    // ===== Timestamp/Timezone =====
328    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
329    pub tz_to_with_time_zone: bool,
330    /// Whether CONVERT_TIMEZONE() is supported
331    pub supports_convert_timezone: bool,
332
333    // ===== JSON =====
334    /// Whether the JSON extraction operators expect a value of type JSON
335    pub json_type_required_for_extraction: bool,
336    /// Whether bracketed keys like ["foo"] are supported in JSON paths
337    pub json_path_bracketed_key_supported: bool,
338    /// Whether to escape keys using single quotes in JSON paths
339    pub json_path_single_quote_escape: bool,
340    /// Whether to quote the generated expression of JsonPath
341    pub quote_json_path: bool,
342    /// What delimiter to use for separating JSON key/value pairs
343    pub json_key_value_pair_sep: &'static str,
344
345    // ===== COPY =====
346    /// Whether parameters from COPY statement are wrapped in parentheses
347    pub copy_params_are_wrapped: bool,
348    /// Whether values of params are set with "=" token or empty space
349    pub copy_params_eq_required: bool,
350    /// Whether COPY statement has INTO keyword
351    pub copy_has_into_keyword: bool,
352
353    // ===== Window functions =====
354    /// Whether EXCLUDE in window specification is supported
355    pub supports_window_exclude: bool,
356    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
357    pub unnest_with_ordinality: bool,
358    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
359    /// Exasol uses lowercase for these specific keywords
360    pub lowercase_window_frame_keywords: bool,
361    /// Whether to normalize single-bound window frames to BETWEEN form
362    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
363    pub normalize_window_frame_between: bool,
364
365    // ===== Array =====
366    /// Whether ARRAY_CONCAT can be generated with varlen args
367    pub array_concat_is_var_len: bool,
368    /// Whether exp.ArraySize should generate the dimension arg too
369    /// None -> Doesn't support, false -> optional, true -> required
370    pub array_size_dim_required: Option<bool>,
371    /// Whether any(f(x) for x in array) can be implemented
372    pub can_implement_array_any: bool,
373    /// Function used for array size
374    pub array_size_name: &'static str,
375
376    // ===== BETWEEN =====
377    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
378    pub supports_between_flags: bool,
379
380    // ===== Boolean =====
381    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
382    pub is_bool_allowed: bool,
383    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
384    pub ensure_bools: bool,
385
386    // ===== EXTRACT =====
387    /// Whether to generate an unquoted value for EXTRACT's date part argument
388    pub extract_allows_quotes: bool,
389    /// Whether to normalize date parts in EXTRACT
390    pub normalize_extract_date_parts: bool,
391
392    // ===== Other features =====
393    /// Whether the conditional TRY(expression) function is supported
394    pub try_supported: bool,
395    /// Whether the UESCAPE syntax in unicode strings is supported
396    pub supports_uescape: bool,
397    /// Whether the function TO_NUMBER is supported
398    pub supports_to_number: bool,
399    /// Whether CONCAT requires >1 arguments
400    pub supports_single_arg_concat: bool,
401    /// Whether LAST_DAY function supports a date part argument
402    pub last_day_supports_date_part: bool,
403    /// Whether a projection can explode into multiple rows
404    pub supports_exploding_projections: bool,
405    /// Whether UNIX_SECONDS(timestamp) is supported
406    pub supports_unix_seconds: bool,
407    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
408    pub supports_like_quantifiers: bool,
409    /// Whether multi-argument DECODE(...) function is supported
410    pub supports_decode_case: bool,
411    /// Whether set op modifiers apply to the outer set op or select
412    pub set_op_modifiers: bool,
413    /// Whether FROM is supported in UPDATE statements
414    pub update_statement_supports_from: bool,
415
416    // ===== COLLATE =====
417    /// Whether COLLATE is a function instead of a binary operator
418    pub collate_is_func: bool,
419
420    // ===== INSERT =====
421    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
422    pub duplicate_key_update_with_set: bool,
423    /// INSERT OVERWRITE TABLE x override
424    pub insert_overwrite: &'static str,
425
426    // ===== RETURNING =====
427    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
428    pub returning_end: bool,
429
430    // ===== MERGE =====
431    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
432    pub matched_by_source: bool,
433
434    // ===== CREATE FUNCTION =====
435    /// Whether create function uses an AS before the RETURN
436    pub create_function_return_as: bool,
437    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
438    pub parameter_default_equals: bool,
439
440    // ===== COMPUTED COLUMN =====
441    /// Whether to include the type of a computed column in the CREATE DDL
442    pub computed_column_with_type: bool,
443
444    // ===== UNPIVOT =====
445    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
446    pub unpivot_aliases_are_identifiers: bool,
447
448    // ===== STAR =====
449    /// The keyword to use when generating a star projection with excluded columns
450    pub star_except: &'static str,
451
452    // ===== HEX =====
453    /// The HEX function name
454    pub hex_func: &'static str,
455
456    // ===== WITH =====
457    /// The keywords to use when prefixing WITH based properties
458    pub with_properties_prefix: &'static str,
459
460    // ===== PAD =====
461    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
462    pub pad_fill_pattern_is_required: bool,
463
464    // ===== INDEX =====
465    /// The string used for creating an index on a table
466    pub index_on: &'static str,
467
468    // ===== GROUPING =====
469    /// The separator for grouping sets and rollups
470    pub groupings_sep: &'static str,
471
472    // ===== STRUCT =====
473    /// Delimiters for STRUCT type
474    pub struct_delimiter: (&'static str, &'static str),
475    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
476    pub struct_curly_brace_notation: bool,
477    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
478    pub array_bracket_only: bool,
479    /// Separator between struct field name and type (": " for Hive, " " for others)
480    pub struct_field_sep: &'static str,
481
482    // ===== EXCEPT/INTERSECT =====
483    /// Whether EXCEPT and INTERSECT operations can return duplicates
484    pub except_intersect_support_all_clause: bool,
485
486    // ===== PARAMETERS/PLACEHOLDERS =====
487    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
488    pub parameter_token: &'static str,
489    /// Named placeholder token (: for most, % for PostgreSQL)
490    pub named_placeholder_token: &'static str,
491
492    // ===== DATA TYPES =====
493    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
494    pub data_type_specifiers_allowed: bool,
495
496    // ===== COMMENT =====
497    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
498    /// StarRocks and Doris use naked COMMENT syntax without `=`
499    pub schema_comment_with_eq: bool,
500}
501
502impl Default for GeneratorConfig {
503    fn default() -> Self {
504        Self {
505            // ===== Basic formatting =====
506            pretty: false,
507            indent: "  ",
508            max_text_width: 80,
509            identifier_quote: '"',
510            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
511            uppercase_keywords: true,
512            normalize_identifiers: false,
513            dialect: None,
514            source_dialect: None,
515            unsupported_level: UnsupportedLevel::Warn,
516            max_unsupported: 3,
517            normalize_functions: NormalizeFunctions::Upper,
518            string_escape: '\'',
519            case_sensitive_identifiers: false,
520            identifiers_can_start_with_digit: false,
521            always_quote_identifiers: false,
522            not_in_style: NotInStyle::Prefix,
523
524            // ===== Null handling =====
525            null_ordering_supported: true,
526            ignore_nulls_in_func: false,
527            nvl2_supported: true,
528
529            // ===== Limit/Fetch =====
530            limit_fetch_style: LimitFetchStyle::Limit,
531            limit_is_top: false,
532            limit_only_literals: false,
533
534            // ===== Interval =====
535            single_string_interval: false,
536            interval_allows_plural_form: true,
537
538            // ===== CTE =====
539            cte_recursive_keyword_required: true,
540
541            // ===== VALUES =====
542            values_as_table: true,
543            wrap_derived_values: true,
544
545            // ===== TABLESAMPLE =====
546            tablesample_seed_keyword: "SEED",
547            tablesample_requires_parens: true,
548            tablesample_size_is_rows: true,
549            tablesample_keywords: "TABLESAMPLE",
550            tablesample_with_method: true,
551            alias_post_tablesample: false,
552
553            // ===== Aggregate =====
554            aggregate_filter_supported: true,
555            multi_arg_distinct: true,
556            quantified_no_paren_space: false,
557            supports_median: true,
558
559            // ===== SELECT =====
560            supports_select_into: false,
561            locking_reads_supported: true,
562
563            // ===== Table/Join =====
564            rename_table_with_db: true,
565            semi_anti_join_with_side: true,
566            supports_table_alias_columns: true,
567            join_hints: true,
568            table_hints: true,
569            query_hints: true,
570            query_hint_sep: ", ",
571            supports_column_join_marks: false,
572
573            // ===== DDL =====
574            index_using_no_space: false,
575            supports_unlogged_tables: false,
576            supports_create_table_like: true,
577            like_property_inside_schema: false,
578            alter_table_include_column_keyword: true,
579            supports_table_copy: true,
580            alter_set_type: "SET DATA TYPE",
581            alter_set_wrapped: false,
582
583            // ===== Timestamp/Timezone =====
584            tz_to_with_time_zone: false,
585            supports_convert_timezone: false,
586
587            // ===== JSON =====
588            json_type_required_for_extraction: false,
589            json_path_bracketed_key_supported: true,
590            json_path_single_quote_escape: false,
591            quote_json_path: true,
592            json_key_value_pair_sep: ":",
593
594            // ===== COPY =====
595            copy_params_are_wrapped: true,
596            copy_params_eq_required: false,
597            copy_has_into_keyword: true,
598
599            // ===== Window functions =====
600            supports_window_exclude: false,
601            unnest_with_ordinality: true,
602            lowercase_window_frame_keywords: false,
603            normalize_window_frame_between: false,
604
605            // ===== Array =====
606            array_concat_is_var_len: true,
607            array_size_dim_required: None,
608            can_implement_array_any: false,
609            array_size_name: "ARRAY_LENGTH",
610
611            // ===== BETWEEN =====
612            supports_between_flags: false,
613
614            // ===== Boolean =====
615            is_bool_allowed: true,
616            ensure_bools: false,
617
618            // ===== EXTRACT =====
619            extract_allows_quotes: true,
620            normalize_extract_date_parts: false,
621
622            // ===== Other features =====
623            try_supported: true,
624            supports_uescape: true,
625            supports_to_number: true,
626            supports_single_arg_concat: true,
627            last_day_supports_date_part: true,
628            supports_exploding_projections: true,
629            supports_unix_seconds: false,
630            supports_like_quantifiers: true,
631            supports_decode_case: true,
632            set_op_modifiers: true,
633            update_statement_supports_from: true,
634
635            // ===== COLLATE =====
636            collate_is_func: false,
637
638            // ===== INSERT =====
639            duplicate_key_update_with_set: true,
640            insert_overwrite: " OVERWRITE TABLE",
641
642            // ===== RETURNING =====
643            returning_end: true,
644
645            // ===== MERGE =====
646            matched_by_source: true,
647
648            // ===== CREATE FUNCTION =====
649            create_function_return_as: true,
650            parameter_default_equals: false,
651
652            // ===== COMPUTED COLUMN =====
653            computed_column_with_type: true,
654
655            // ===== UNPIVOT =====
656            unpivot_aliases_are_identifiers: true,
657
658            // ===== STAR =====
659            star_except: "EXCEPT",
660
661            // ===== HEX =====
662            hex_func: "HEX",
663
664            // ===== WITH =====
665            with_properties_prefix: "WITH",
666
667            // ===== PAD =====
668            pad_fill_pattern_is_required: false,
669
670            // ===== INDEX =====
671            index_on: "ON",
672
673            // ===== GROUPING =====
674            groupings_sep: ",",
675
676            // ===== STRUCT =====
677            struct_delimiter: ("<", ">"),
678            struct_curly_brace_notation: false,
679            array_bracket_only: false,
680            struct_field_sep: " ",
681
682            // ===== EXCEPT/INTERSECT =====
683            except_intersect_support_all_clause: true,
684
685            // ===== PARAMETERS/PLACEHOLDERS =====
686            parameter_token: "@",
687            named_placeholder_token: ":",
688
689            // ===== DATA TYPES =====
690            data_type_specifiers_allowed: false,
691
692            // ===== COMMENT =====
693            schema_comment_with_eq: true,
694        }
695    }
696}
697
698/// SQL reserved keywords that require quoting when used as identifiers
699/// Based on ANSI SQL standards and common dialect-specific reserved words
700mod reserved_keywords {
701    use std::collections::HashSet;
702    use std::sync::LazyLock;
703
704    /// Standard SQL reserved keywords (ANSI SQL:2016)
705    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
706        [
707            "all",
708            "alter",
709            "and",
710            "any",
711            "array",
712            "as",
713            "asc",
714            "at",
715            "authorization",
716            "begin",
717            "between",
718            "both",
719            "by",
720            "case",
721            "cast",
722            "check",
723            "collate",
724            "column",
725            "commit",
726            "constraint",
727            "create",
728            "cross",
729            "cube",
730            "current",
731            "current_date",
732            "current_time",
733            "current_timestamp",
734            "current_user",
735            "default",
736            "delete",
737            "desc",
738            "distinct",
739            "drop",
740            "else",
741            "end",
742            "escape",
743            "except",
744            "execute",
745            "exists",
746            "external",
747            "false",
748            "fetch",
749            "filter",
750            "for",
751            "foreign",
752            "from",
753            "full",
754            "function",
755            "grant",
756            "group",
757            "grouping",
758            "having",
759            "if",
760            "in",
761            "index",
762            "inner",
763            "insert",
764            "intersect",
765            "interval",
766            "into",
767            "is",
768            "join",
769            "key",
770            "leading",
771            "left",
772            "like",
773            "limit",
774            "local",
775            "localtime",
776            "localtimestamp",
777            "match",
778            "merge",
779            "natural",
780            "no",
781            "not",
782            "null",
783            "of",
784            "offset",
785            "on",
786            "only",
787            "or",
788            "order",
789            "outer",
790            "over",
791            "partition",
792            "primary",
793            "procedure",
794            "range",
795            "references",
796            "right",
797            "rollback",
798            "rollup",
799            "row",
800            "rows",
801            "select",
802            "session_user",
803            "set",
804            "some",
805            "table",
806            "tablesample",
807            "then",
808            "to",
809            "trailing",
810            "true",
811            "truncate",
812            "union",
813            "unique",
814            "unknown",
815            "update",
816            "user",
817            "using",
818            "values",
819            "view",
820            "when",
821            "where",
822            "window",
823            "with",
824        ]
825        .into_iter()
826        .collect()
827    });
828
829    /// BigQuery-specific reserved keywords
830    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
831    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
832        let mut set = SQL_RESERVED.clone();
833        set.extend([
834            "assert_rows_modified",
835            "at",
836            "contains",
837            "cube",
838            "current",
839            "define",
840            "enum",
841            "escape",
842            "exclude",
843            "following",
844            "for",
845            "groups",
846            "hash",
847            "ignore",
848            "lateral",
849            "lookup",
850            "new",
851            "no",
852            "nulls",
853            "of",
854            "over",
855            "preceding",
856            "proto",
857            "qualify",
858            "recursive",
859            "respect",
860            "struct",
861            "tablesample",
862            "treat",
863            "unbounded",
864            "unnest",
865            "window",
866            "within",
867        ]);
868        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
869        set.remove("grant");
870        set.remove("key");
871        set.remove("index");
872        set.remove("offset");
873        set.remove("values");
874        set.remove("table");
875        set
876    });
877
878    /// MySQL-specific reserved keywords
879    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
880        let mut set = SQL_RESERVED.clone();
881        set.extend([
882            "accessible",
883            "add",
884            "analyze",
885            "asensitive",
886            "before",
887            "bigint",
888            "binary",
889            "blob",
890            "call",
891            "cascade",
892            "change",
893            "char",
894            "character",
895            "condition",
896            "continue",
897            "convert",
898            "current_date",
899            "current_time",
900            "current_timestamp",
901            "current_user",
902            "cursor",
903            "database",
904            "databases",
905            "day_hour",
906            "day_microsecond",
907            "day_minute",
908            "day_second",
909            "dec",
910            "decimal",
911            "declare",
912            "delayed",
913            "describe",
914            "deterministic",
915            "distinctrow",
916            "div",
917            "double",
918            "dual",
919            "each",
920            "elseif",
921            "enclosed",
922            "escaped",
923            "exit",
924            "explain",
925            "float",
926            "float4",
927            "float8",
928            "force",
929            "get",
930            "high_priority",
931            "hour_microsecond",
932            "hour_minute",
933            "hour_second",
934            "ignore",
935            "infile",
936            "inout",
937            "insensitive",
938            "int",
939            "int1",
940            "int2",
941            "int3",
942            "int4",
943            "int8",
944            "integer",
945            "iterate",
946            "keys",
947            "kill",
948            "leave",
949            "linear",
950            "lines",
951            "load",
952            "lock",
953            "long",
954            "longblob",
955            "longtext",
956            "loop",
957            "low_priority",
958            "master_ssl_verify_server_cert",
959            "maxvalue",
960            "mediumblob",
961            "mediumint",
962            "mediumtext",
963            "middleint",
964            "minute_microsecond",
965            "minute_second",
966            "mod",
967            "modifies",
968            "no_write_to_binlog",
969            "numeric",
970            "optimize",
971            "option",
972            "optionally",
973            "out",
974            "outfile",
975            "precision",
976            "purge",
977            "read",
978            "reads",
979            "real",
980            "regexp",
981            "release",
982            "rename",
983            "repeat",
984            "replace",
985            "require",
986            "resignal",
987            "restrict",
988            "return",
989            "revoke",
990            "rlike",
991            "schema",
992            "schemas",
993            "second_microsecond",
994            "sensitive",
995            "separator",
996            "show",
997            "signal",
998            "smallint",
999            "spatial",
1000            "specific",
1001            "sql",
1002            "sql_big_result",
1003            "sql_calc_found_rows",
1004            "sql_small_result",
1005            "sqlexception",
1006            "sqlstate",
1007            "sqlwarning",
1008            "ssl",
1009            "starting",
1010            "straight_join",
1011            "terminated",
1012            "text",
1013            "tinyblob",
1014            "tinyint",
1015            "tinytext",
1016            "trigger",
1017            "undo",
1018            "unlock",
1019            "unsigned",
1020            "usage",
1021            "utc_date",
1022            "utc_time",
1023            "utc_timestamp",
1024            "varbinary",
1025            "varchar",
1026            "varcharacter",
1027            "varying",
1028            "while",
1029            "write",
1030            "xor",
1031            "year_month",
1032            "zerofill",
1033        ]);
1034        set.remove("table");
1035        set
1036    });
1037
1038    /// Doris-specific reserved keywords
1039    /// Extends MySQL reserved with additional Doris-specific words
1040    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1041        let mut set = MYSQL_RESERVED.clone();
1042        set.extend([
1043            "aggregate",
1044            "anti",
1045            "array",
1046            "backend",
1047            "backup",
1048            "begin",
1049            "bitmap",
1050            "boolean",
1051            "broker",
1052            "buckets",
1053            "cached",
1054            "cancel",
1055            "cast",
1056            "catalog",
1057            "charset",
1058            "cluster",
1059            "collation",
1060            "columns",
1061            "comment",
1062            "commit",
1063            "config",
1064            "connection",
1065            "count",
1066            "current",
1067            "data",
1068            "date",
1069            "datetime",
1070            "day",
1071            "deferred",
1072            "distributed",
1073            "dynamic",
1074            "enable",
1075            "end",
1076            "events",
1077            "export",
1078            "external",
1079            "fields",
1080            "first",
1081            "follower",
1082            "format",
1083            "free",
1084            "frontend",
1085            "full",
1086            "functions",
1087            "global",
1088            "grants",
1089            "hash",
1090            "help",
1091            "hour",
1092            "install",
1093            "intermediate",
1094            "json",
1095            "label",
1096            "last",
1097            "less",
1098            "level",
1099            "link",
1100            "local",
1101            "location",
1102            "max",
1103            "merge",
1104            "min",
1105            "minute",
1106            "modify",
1107            "month",
1108            "name",
1109            "names",
1110            "negative",
1111            "nulls",
1112            "observer",
1113            "offset",
1114            "only",
1115            "open",
1116            "overwrite",
1117            "password",
1118            "path",
1119            "plan",
1120            "plugin",
1121            "plugins",
1122            "policy",
1123            "process",
1124            "properties",
1125            "property",
1126            "query",
1127            "quota",
1128            "recover",
1129            "refresh",
1130            "repair",
1131            "replica",
1132            "repository",
1133            "resource",
1134            "restore",
1135            "resume",
1136            "role",
1137            "roles",
1138            "rollback",
1139            "rollup",
1140            "routine",
1141            "sample",
1142            "second",
1143            "semi",
1144            "session",
1145            "signed",
1146            "snapshot",
1147            "start",
1148            "stats",
1149            "status",
1150            "stop",
1151            "stream",
1152            "string",
1153            "sum",
1154            "tables",
1155            "tablet",
1156            "temporary",
1157            "text",
1158            "timestamp",
1159            "transaction",
1160            "trash",
1161            "trim",
1162            "truncate",
1163            "type",
1164            "user",
1165            "value",
1166            "variables",
1167            "verbose",
1168            "version",
1169            "view",
1170            "warnings",
1171            "week",
1172            "work",
1173            "year",
1174        ]);
1175        set
1176    });
1177
1178    /// PostgreSQL-specific reserved keywords
1179    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1180        let mut set = SQL_RESERVED.clone();
1181        set.extend([
1182            "analyse",
1183            "analyze",
1184            "asymmetric",
1185            "binary",
1186            "collation",
1187            "concurrently",
1188            "current_catalog",
1189            "current_role",
1190            "current_schema",
1191            "deferrable",
1192            "do",
1193            "freeze",
1194            "ilike",
1195            "initially",
1196            "isnull",
1197            "lateral",
1198            "notnull",
1199            "placing",
1200            "returning",
1201            "similar",
1202            "symmetric",
1203            "variadic",
1204            "verbose",
1205        ]);
1206        // PostgreSQL doesn't require quoting for these keywords
1207        set.remove("default");
1208        set.remove("interval");
1209        set.remove("match");
1210        set.remove("offset");
1211        set.remove("table");
1212        set
1213    });
1214
1215    /// Redshift-specific reserved keywords
1216    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1217    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1218    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1219        [
1220            "aes128",
1221            "aes256",
1222            "all",
1223            "allowoverwrite",
1224            "analyse",
1225            "analyze",
1226            "and",
1227            "any",
1228            "array",
1229            "as",
1230            "asc",
1231            "authorization",
1232            "az64",
1233            "backup",
1234            "between",
1235            "binary",
1236            "blanksasnull",
1237            "both",
1238            "bytedict",
1239            "bzip2",
1240            "case",
1241            "cast",
1242            "check",
1243            "collate",
1244            "column",
1245            "constraint",
1246            "create",
1247            "credentials",
1248            "cross",
1249            "current_date",
1250            "current_time",
1251            "current_timestamp",
1252            "current_user",
1253            "current_user_id",
1254            "default",
1255            "deferrable",
1256            "deflate",
1257            "defrag",
1258            "delta",
1259            "delta32k",
1260            "desc",
1261            "disable",
1262            "distinct",
1263            "do",
1264            "else",
1265            "emptyasnull",
1266            "enable",
1267            "encode",
1268            "encrypt",
1269            "encryption",
1270            "end",
1271            "except",
1272            "explicit",
1273            "false",
1274            "for",
1275            "foreign",
1276            "freeze",
1277            "from",
1278            "full",
1279            "globaldict256",
1280            "globaldict64k",
1281            "grant",
1282            "group",
1283            "gzip",
1284            "having",
1285            "identity",
1286            "ignore",
1287            "ilike",
1288            "in",
1289            "initially",
1290            "inner",
1291            "intersect",
1292            "interval",
1293            "into",
1294            "is",
1295            "isnull",
1296            "join",
1297            "leading",
1298            "left",
1299            "like",
1300            "limit",
1301            "localtime",
1302            "localtimestamp",
1303            "lun",
1304            "luns",
1305            "lzo",
1306            "lzop",
1307            "minus",
1308            "mostly16",
1309            "mostly32",
1310            "mostly8",
1311            "natural",
1312            "new",
1313            "not",
1314            "notnull",
1315            "null",
1316            "nulls",
1317            "off",
1318            "offline",
1319            "offset",
1320            "oid",
1321            "old",
1322            "on",
1323            "only",
1324            "open",
1325            "or",
1326            "order",
1327            "outer",
1328            "overlaps",
1329            "parallel",
1330            "partition",
1331            "percent",
1332            "permissions",
1333            "pivot",
1334            "placing",
1335            "primary",
1336            "raw",
1337            "readratio",
1338            "recover",
1339            "references",
1340            "rejectlog",
1341            "resort",
1342            "respect",
1343            "restore",
1344            "right",
1345            "select",
1346            "session_user",
1347            "similar",
1348            "snapshot",
1349            "some",
1350            "sysdate",
1351            "system",
1352            "table",
1353            "tag",
1354            "tdes",
1355            "text255",
1356            "text32k",
1357            "then",
1358            "timestamp",
1359            "to",
1360            "top",
1361            "trailing",
1362            "true",
1363            "truncatecolumns",
1364            "type",
1365            "union",
1366            "unique",
1367            "unnest",
1368            "unpivot",
1369            "user",
1370            "using",
1371            "verbose",
1372            "wallet",
1373            "when",
1374            "where",
1375            "with",
1376            "without",
1377        ]
1378        .into_iter()
1379        .collect()
1380    });
1381
1382    /// DuckDB-specific reserved keywords
1383    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1384        let mut set = POSTGRES_RESERVED.clone();
1385        set.extend([
1386            "anti",
1387            "asof",
1388            "columns",
1389            "describe",
1390            "groups",
1391            "macro",
1392            "pivot",
1393            "pivot_longer",
1394            "pivot_wider",
1395            "qualify",
1396            "replace",
1397            "respect",
1398            "semi",
1399            "show",
1400            "table",
1401            "unpivot",
1402        ]);
1403        set.remove("at");
1404        set.remove("key");
1405        set.remove("range");
1406        set.remove("row");
1407        set.remove("values");
1408        set
1409    });
1410
1411    /// Presto/Trino/Athena-specific reserved keywords
1412    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1413        let mut set = SQL_RESERVED.clone();
1414        set.extend([
1415            "alter",
1416            "and",
1417            "as",
1418            "between",
1419            "by",
1420            "case",
1421            "cast",
1422            "constraint",
1423            "create",
1424            "cross",
1425            "cube",
1426            "current_catalog",
1427            "current_date",
1428            "current_path",
1429            "current_role",
1430            "current_schema",
1431            "current_time",
1432            "current_timestamp",
1433            "current_user",
1434            "deallocate",
1435            "delete",
1436            "describe",
1437            "distinct",
1438            "drop",
1439            "else",
1440            "end",
1441            "escape",
1442            "except",
1443            "execute",
1444            "exists",
1445            "extract",
1446            "false",
1447            "for",
1448            "from",
1449            "full",
1450            "group",
1451            "grouping",
1452            "having",
1453            "in",
1454            "inner",
1455            "insert",
1456            "intersect",
1457            "into",
1458            "is",
1459            "join",
1460            "json_array",
1461            "json_exists",
1462            "json_object",
1463            "json_query",
1464            "json_table",
1465            "json_value",
1466            "left",
1467            "like",
1468            "listagg",
1469            "localtime",
1470            "localtimestamp",
1471            "natural",
1472            "normalize",
1473            "not",
1474            "null",
1475            "on",
1476            "or",
1477            "order",
1478            "outer",
1479            "prepare",
1480            "recursive",
1481            "right",
1482            "rollup",
1483            "select",
1484            "skip",
1485            "table",
1486            "then",
1487            "trim",
1488            "true",
1489            "uescape",
1490            "union",
1491            "unnest",
1492            "using",
1493            "values",
1494            "when",
1495            "where",
1496            "with",
1497        ]);
1498        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1499        set.remove("key");
1500        set
1501    });
1502
1503    /// StarRocks-specific reserved keywords
1504    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1505    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1506        [
1507            "add",
1508            "all",
1509            "alter",
1510            "analyze",
1511            "and",
1512            "array",
1513            "as",
1514            "asc",
1515            "between",
1516            "bigint",
1517            "bitmap",
1518            "both",
1519            "by",
1520            "case",
1521            "char",
1522            "character",
1523            "check",
1524            "collate",
1525            "column",
1526            "compaction",
1527            "convert",
1528            "create",
1529            "cross",
1530            "cube",
1531            "current_date",
1532            "current_role",
1533            "current_time",
1534            "current_timestamp",
1535            "current_user",
1536            "database",
1537            "databases",
1538            "decimal",
1539            "decimalv2",
1540            "decimal32",
1541            "decimal64",
1542            "decimal128",
1543            "default",
1544            "deferred",
1545            "delete",
1546            "dense_rank",
1547            "desc",
1548            "describe",
1549            "distinct",
1550            "double",
1551            "drop",
1552            "dual",
1553            "else",
1554            "except",
1555            "exists",
1556            "explain",
1557            "false",
1558            "first_value",
1559            "float",
1560            "for",
1561            "force",
1562            "from",
1563            "full",
1564            "function",
1565            "grant",
1566            "group",
1567            "grouping",
1568            "grouping_id",
1569            "groups",
1570            "having",
1571            "hll",
1572            "host",
1573            "if",
1574            "ignore",
1575            "immediate",
1576            "in",
1577            "index",
1578            "infile",
1579            "inner",
1580            "insert",
1581            "int",
1582            "integer",
1583            "intersect",
1584            "into",
1585            "is",
1586            "join",
1587            "json",
1588            "key",
1589            "keys",
1590            "kill",
1591            "lag",
1592            "largeint",
1593            "last_value",
1594            "lateral",
1595            "lead",
1596            "left",
1597            "like",
1598            "limit",
1599            "load",
1600            "localtime",
1601            "localtimestamp",
1602            "maxvalue",
1603            "minus",
1604            "mod",
1605            "not",
1606            "ntile",
1607            "null",
1608            "on",
1609            "or",
1610            "order",
1611            "outer",
1612            "outfile",
1613            "over",
1614            "partition",
1615            "percentile",
1616            "primary",
1617            "procedure",
1618            "qualify",
1619            "range",
1620            "rank",
1621            "read",
1622            "regexp",
1623            "release",
1624            "rename",
1625            "replace",
1626            "revoke",
1627            "right",
1628            "rlike",
1629            "row",
1630            "row_number",
1631            "rows",
1632            "schema",
1633            "schemas",
1634            "select",
1635            "set",
1636            "set_var",
1637            "show",
1638            "smallint",
1639            "system",
1640            "table",
1641            "terminated",
1642            "text",
1643            "then",
1644            "tinyint",
1645            "to",
1646            "true",
1647            "union",
1648            "unique",
1649            "unsigned",
1650            "update",
1651            "use",
1652            "using",
1653            "values",
1654            "varchar",
1655            "when",
1656            "where",
1657            "with",
1658        ]
1659        .into_iter()
1660        .collect()
1661    });
1662
1663    /// SingleStore-specific reserved keywords
1664    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1665    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1666        let mut set = MYSQL_RESERVED.clone();
1667        set.extend([
1668            // Additional SingleStore reserved keywords from Python sqlglot
1669            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1670            "abs",
1671            "account",
1672            "acos",
1673            "adddate",
1674            "addtime",
1675            "admin",
1676            "aes_decrypt",
1677            "aes_encrypt",
1678            "aggregate",
1679            "aggregates",
1680            "aggregator",
1681            "anti_join",
1682            "any_value",
1683            "approx_count_distinct",
1684            "approx_percentile",
1685            "arrange",
1686            "arrangement",
1687            "asin",
1688            "atan",
1689            "atan2",
1690            "attach",
1691            "autostats",
1692            "avro",
1693            "background",
1694            "backup",
1695            "batch",
1696            "batches",
1697            "boot_strapping",
1698            "ceil",
1699            "ceiling",
1700            "coercibility",
1701            "columnar",
1702            "columnstore",
1703            "compile",
1704            "concurrent",
1705            "connection_id",
1706            "cos",
1707            "cot",
1708            "current_security_groups",
1709            "current_security_roles",
1710            "dayname",
1711            "dayofmonth",
1712            "dayofweek",
1713            "dayofyear",
1714            "degrees",
1715            "dot_product",
1716            "dump",
1717            "durability",
1718            "earliest",
1719            "echo",
1720            "election",
1721            "euclidean_distance",
1722            "exp",
1723            "extractor",
1724            "extractors",
1725            "floor",
1726            "foreground",
1727            "found_rows",
1728            "from_base64",
1729            "from_days",
1730            "from_unixtime",
1731            "fs",
1732            "fulltext",
1733            "gc",
1734            "gcs",
1735            "geography",
1736            "geography_area",
1737            "geography_contains",
1738            "geography_distance",
1739            "geography_intersects",
1740            "geography_latitude",
1741            "geography_length",
1742            "geography_longitude",
1743            "geographypoint",
1744            "geography_point",
1745            "geography_within_distance",
1746            "geometry",
1747            "geometry_area",
1748            "geometry_contains",
1749            "geometry_distance",
1750            "geometry_filter",
1751            "geometry_intersects",
1752            "geometry_length",
1753            "geometrypoint",
1754            "geometry_point",
1755            "geometry_within_distance",
1756            "geometry_x",
1757            "geometry_y",
1758            "greatest",
1759            "groups",
1760            "group_concat",
1761            "gzip",
1762            "hdfs",
1763            "hex",
1764            "highlight",
1765            "ifnull",
1766            "ilike",
1767            "inet_aton",
1768            "inet_ntoa",
1769            "inet6_aton",
1770            "inet6_ntoa",
1771            "initcap",
1772            "instr",
1773            "interpreter_mode",
1774            "isnull",
1775            "json",
1776            "json_agg",
1777            "json_array_contains_double",
1778            "json_array_contains_json",
1779            "json_array_contains_string",
1780            "json_delete_key",
1781            "json_extract_double",
1782            "json_extract_json",
1783            "json_extract_string",
1784            "json_extract_bigint",
1785            "json_get_type",
1786            "json_length",
1787            "json_set_double",
1788            "json_set_json",
1789            "json_set_string",
1790            "kafka",
1791            "lag",
1792            "last_day",
1793            "last_insert_id",
1794            "latest",
1795            "lcase",
1796            "lead",
1797            "leaf",
1798            "least",
1799            "leaves",
1800            "length",
1801            "license",
1802            "links",
1803            "llvm",
1804            "ln",
1805            "load",
1806            "locate",
1807            "log",
1808            "log10",
1809            "log2",
1810            "lpad",
1811            "lz4",
1812            "management",
1813            "match",
1814            "mbc",
1815            "md5",
1816            "median",
1817            "memsql",
1818            "memsql_deserialize",
1819            "memsql_serialize",
1820            "metadata",
1821            "microsecond",
1822            "minute",
1823            "model",
1824            "monthname",
1825            "months_between",
1826            "mpl",
1827            "namespace",
1828            "node",
1829            "noparam",
1830            "now",
1831            "nth_value",
1832            "ntile",
1833            "nullcols",
1834            "nullif",
1835            "object",
1836            "octet_length",
1837            "offsets",
1838            "online",
1839            "optimizer",
1840            "orphan",
1841            "parquet",
1842            "partitions",
1843            "pause",
1844            "percentile_cont",
1845            "percentile_disc",
1846            "periodic",
1847            "persisted",
1848            "pi",
1849            "pipeline",
1850            "pipelines",
1851            "plancache",
1852            "plugins",
1853            "pool",
1854            "pools",
1855            "pow",
1856            "power",
1857            "process",
1858            "processlist",
1859            "profile",
1860            "profiles",
1861            "quarter",
1862            "queries",
1863            "query",
1864            "radians",
1865            "rand",
1866            "record",
1867            "reduce",
1868            "redundancy",
1869            "regexp_match",
1870            "regexp_substr",
1871            "remote",
1872            "replication",
1873            "resource",
1874            "resource_pool",
1875            "restore",
1876            "retry",
1877            "role",
1878            "roles",
1879            "round",
1880            "rpad",
1881            "rtrim",
1882            "running",
1883            "s3",
1884            "scalar",
1885            "sec_to_time",
1886            "second",
1887            "security_lists_intersect",
1888            "semi_join",
1889            "sha",
1890            "sha1",
1891            "sha2",
1892            "shard",
1893            "sharded",
1894            "sharded_id",
1895            "sigmoid",
1896            "sign",
1897            "sin",
1898            "skip",
1899            "sleep",
1900            "snapshot",
1901            "soname",
1902            "sparse",
1903            "spatial_check_index",
1904            "split",
1905            "sqrt",
1906            "standalone",
1907            "std",
1908            "stddev",
1909            "stddev_pop",
1910            "stddev_samp",
1911            "stop",
1912            "str_to_date",
1913            "subdate",
1914            "substr",
1915            "substring_index",
1916            "success",
1917            "synchronize",
1918            "table_checksum",
1919            "tan",
1920            "task",
1921            "timediff",
1922            "time_bucket",
1923            "time_format",
1924            "time_to_sec",
1925            "timestampadd",
1926            "timestampdiff",
1927            "to_base64",
1928            "to_char",
1929            "to_date",
1930            "to_days",
1931            "to_json",
1932            "to_number",
1933            "to_seconds",
1934            "to_timestamp",
1935            "tracelogs",
1936            "transform",
1937            "trim",
1938            "trunc",
1939            "truncate",
1940            "ucase",
1941            "unhex",
1942            "unix_timestamp",
1943            "utc_date",
1944            "utc_time",
1945            "utc_timestamp",
1946            "vacuum",
1947            "variance",
1948            "var_pop",
1949            "var_samp",
1950            "vector_sub",
1951            "voting",
1952            "week",
1953            "weekday",
1954            "weekofyear",
1955            "workload",
1956            "year",
1957        ]);
1958        // Remove "all" because ORDER BY ALL needs ALL unquoted
1959        set.remove("all");
1960        set
1961    });
1962
1963    /// SQLite-specific reserved keywords
1964    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1965    /// Reference: https://www.sqlite.org/lang_keywords.html
1966    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1967        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1968        [
1969            "abort",
1970            "action",
1971            "add",
1972            "after",
1973            "all",
1974            "alter",
1975            "always",
1976            "analyze",
1977            "and",
1978            "as",
1979            "asc",
1980            "attach",
1981            "autoincrement",
1982            "before",
1983            "begin",
1984            "between",
1985            "by",
1986            "cascade",
1987            "case",
1988            "cast",
1989            "check",
1990            "collate",
1991            "column",
1992            "commit",
1993            "conflict",
1994            "constraint",
1995            "create",
1996            "cross",
1997            "current",
1998            "current_date",
1999            "current_time",
2000            "current_timestamp",
2001            "database",
2002            "default",
2003            "deferrable",
2004            "deferred",
2005            "delete",
2006            "desc",
2007            "detach",
2008            "distinct",
2009            "do",
2010            "drop",
2011            "each",
2012            "else",
2013            "end",
2014            "escape",
2015            "except",
2016            "exclude",
2017            "exclusive",
2018            "exists",
2019            "explain",
2020            "fail",
2021            "filter",
2022            "first",
2023            "following",
2024            "for",
2025            "foreign",
2026            "from",
2027            "full",
2028            "generated",
2029            "glob",
2030            "group",
2031            "groups",
2032            "having",
2033            "if",
2034            "ignore",
2035            "immediate",
2036            "in",
2037            "index",
2038            "indexed",
2039            "initially",
2040            "inner",
2041            "insert",
2042            "instead",
2043            "intersect",
2044            "into",
2045            "is",
2046            "isnull",
2047            "join",
2048            "key",
2049            "last",
2050            "left",
2051            "like",
2052            "limit",
2053            "natural",
2054            "no",
2055            "not",
2056            "nothing",
2057            "notnull",
2058            "null",
2059            "nulls",
2060            "of",
2061            "offset",
2062            "on",
2063            "or",
2064            "order",
2065            "others",
2066            "outer",
2067            "partition",
2068            "plan",
2069            "pragma",
2070            "preceding",
2071            "primary",
2072            "query",
2073            "raise",
2074            "range",
2075            "recursive",
2076            "references",
2077            "regexp",
2078            "reindex",
2079            "release",
2080            "rename",
2081            "replace",
2082            "restrict",
2083            "returning",
2084            "right",
2085            "rollback",
2086            "row",
2087            "rows",
2088            "savepoint",
2089            "select",
2090            "set",
2091            "table",
2092            "temp",
2093            "temporary",
2094            "then",
2095            "ties",
2096            "to",
2097            "transaction",
2098            "trigger",
2099            "unbounded",
2100            "union",
2101            "unique",
2102            "update",
2103            "using",
2104            "vacuum",
2105            "values",
2106            "view",
2107            "virtual",
2108            "when",
2109            "where",
2110            "window",
2111            "with",
2112            "without",
2113        ]
2114        .into_iter()
2115        .collect()
2116    });
2117}
2118
2119impl Generator {
2120    /// Create a new generator with the default configuration.
2121    ///
2122    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2123    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2124    /// and no dialect-specific transformations.
2125    pub fn new() -> Self {
2126        Self::with_config(GeneratorConfig::default())
2127    }
2128
2129    /// Create a generator with a custom [`GeneratorConfig`].
2130    ///
2131    /// Use this when you need dialect-specific output, pretty-printing, or other
2132    /// non-default settings.
2133    pub fn with_config(config: GeneratorConfig) -> Self {
2134        Self::with_arc_config(Arc::new(config))
2135    }
2136
2137    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2138    ///
2139    /// This avoids cloning the configuration when multiple generators share the
2140    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2141    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2142        Self {
2143            config,
2144            output: String::new(),
2145            unsupported_messages: Vec::new(),
2146            indent_level: 0,
2147            athena_hive_context: false,
2148            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2149            merge_strip_qualifiers: Vec::new(),
2150            clickhouse_nullable_depth: 0,
2151        }
2152    }
2153
2154    /// Add column aliases to a query expression for TSQL SELECT INTO.
2155    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2156    /// Recursively processes all SELECT expressions in the query tree.
2157    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2158        match expr {
2159            Expression::Select(mut select) => {
2160                // Add aliases to all select expressions that don't already have them
2161                select.expressions = select
2162                    .expressions
2163                    .into_iter()
2164                    .map(|e| Self::add_alias_to_expression(e))
2165                    .collect();
2166
2167                // Recursively process subqueries in FROM clause
2168                if let Some(ref mut from) = select.from {
2169                    from.expressions = from
2170                        .expressions
2171                        .iter()
2172                        .cloned()
2173                        .map(|e| Self::add_column_aliases_to_query(e))
2174                        .collect();
2175                }
2176
2177                Expression::Select(select)
2178            }
2179            Expression::Subquery(mut sq) => {
2180                sq.this = Self::add_column_aliases_to_query(sq.this);
2181                Expression::Subquery(sq)
2182            }
2183            Expression::Paren(mut p) => {
2184                p.this = Self::add_column_aliases_to_query(p.this);
2185                Expression::Paren(p)
2186            }
2187            // For other expressions (Union, Intersect, etc.), pass through
2188            other => other,
2189        }
2190    }
2191
2192    /// Add an alias to a single select expression if it doesn't already have one.
2193    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2194    fn add_alias_to_expression(expr: Expression) -> Expression {
2195        use crate::expressions::Alias;
2196
2197        match &expr {
2198            // Already aliased - just return it
2199            Expression::Alias(_) => expr,
2200
2201            // Column reference: add alias from column name
2202            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2203                this: expr.clone(),
2204                alias: col.name.clone(),
2205                column_aliases: Vec::new(),
2206                alias_explicit_as: false,
2207                alias_keyword: None,
2208                pre_alias_comments: Vec::new(),
2209                trailing_comments: Vec::new(),
2210                inferred_type: None,
2211            })),
2212
2213            // Identifier: add alias from identifier name
2214            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2215                this: expr.clone(),
2216                alias: ident.clone(),
2217                column_aliases: Vec::new(),
2218                alias_explicit_as: false,
2219                alias_keyword: None,
2220                pre_alias_comments: Vec::new(),
2221                trailing_comments: Vec::new(),
2222                inferred_type: None,
2223            })),
2224
2225            // Subquery: recursively process and add alias if inner returns a named column
2226            Expression::Subquery(sq) => {
2227                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2228                // Subqueries that are already aliased keep their alias
2229                if sq.alias.is_some() {
2230                    processed
2231                } else {
2232                    // If there's no alias, keep it as-is (let TSQL handle it)
2233                    processed
2234                }
2235            }
2236
2237            // Star expressions (*) - don't alias
2238            Expression::Star(_) => expr,
2239
2240            // For other expressions, don't add an alias
2241            // (function calls, literals, etc. would need explicit aliases anyway)
2242            _ => expr,
2243        }
2244    }
2245
2246    /// Try to evaluate a constant arithmetic expression to a number literal.
2247    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2248    /// otherwise returns the original expression.
2249    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2250        match expr {
2251            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2252                let Literal::Number(n) = lit.as_ref() else {
2253                    unreachable!()
2254                };
2255                n.parse::<i64>().ok()
2256            }
2257            Expression::Add(op) => {
2258                let left = Self::try_evaluate_constant(&op.left)?;
2259                let right = Self::try_evaluate_constant(&op.right)?;
2260                Some(left + right)
2261            }
2262            Expression::Sub(op) => {
2263                let left = Self::try_evaluate_constant(&op.left)?;
2264                let right = Self::try_evaluate_constant(&op.right)?;
2265                Some(left - right)
2266            }
2267            Expression::Mul(op) => {
2268                let left = Self::try_evaluate_constant(&op.left)?;
2269                let right = Self::try_evaluate_constant(&op.right)?;
2270                Some(left * right)
2271            }
2272            Expression::Div(op) => {
2273                let left = Self::try_evaluate_constant(&op.left)?;
2274                let right = Self::try_evaluate_constant(&op.right)?;
2275                if right != 0 {
2276                    Some(left / right)
2277                } else {
2278                    None
2279                }
2280            }
2281            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2282            _ => None,
2283        }
2284    }
2285
2286    /// Check if an identifier is a reserved keyword for the current dialect
2287    fn is_reserved_keyword(&self, name: &str) -> bool {
2288        use crate::dialects::DialectType;
2289        let mut buf = [0u8; 128];
2290        let lower_ref: &str = if name.len() <= 128 {
2291            for (i, b) in name.bytes().enumerate() {
2292                buf[i] = b.to_ascii_lowercase();
2293            }
2294            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2295            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2296        } else {
2297            return false;
2298        };
2299
2300        match self.config.dialect {
2301            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2302            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2303                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2304            }
2305            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2306            Some(DialectType::SingleStore) => {
2307                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2308            }
2309            Some(DialectType::StarRocks) => {
2310                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2311            }
2312            Some(DialectType::PostgreSQL)
2313            | Some(DialectType::CockroachDB)
2314            | Some(DialectType::Materialize)
2315            | Some(DialectType::RisingWave) => {
2316                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2317            }
2318            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2319            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2320            // meaning it never quotes identifiers based on reserved word status.
2321            Some(DialectType::Snowflake) => false,
2322            // ClickHouse: don't quote reserved keywords to preserve identity output
2323            Some(DialectType::ClickHouse) => false,
2324            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2325            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2326            Some(DialectType::Teradata) => false,
2327            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2328            Some(DialectType::TSQL)
2329            | Some(DialectType::Fabric)
2330            | Some(DialectType::Oracle)
2331            | Some(DialectType::Spark)
2332            | Some(DialectType::Databricks)
2333            | Some(DialectType::Hive)
2334            | Some(DialectType::Solr) => false,
2335            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2336                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2337            }
2338            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2339            // For Generic dialect or None, don't add extra quoting to preserve identity
2340            Some(DialectType::Generic) | None => false,
2341            // For other dialects, use standard SQL reserved keywords
2342            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2343        }
2344    }
2345
2346    /// Normalize function name based on dialect settings
2347    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2348        match self.config.normalize_functions {
2349            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2350            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2351            NormalizeFunctions::None => Cow::Borrowed(name),
2352        }
2353    }
2354
2355    /// Generate a SQL string from an AST expression.
2356    ///
2357    /// This is the primary generation method. It clears any previous internal state,
2358    /// walks the expression tree, and returns the resulting SQL text. The output
2359    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2360    ///
2361    /// The generator can be reused across multiple calls; each call to `generate`
2362    /// resets the internal buffer.
2363    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2364        self.output.clear();
2365        self.unsupported_messages.clear();
2366        self.generate_expression(expr)?;
2367        if self.config.unsupported_level == UnsupportedLevel::Raise
2368            && !self.unsupported_messages.is_empty()
2369        {
2370            return Err(crate::error::Error::generate(
2371                self.format_unsupported_messages(),
2372            ));
2373        }
2374        Ok(std::mem::take(&mut self.output))
2375    }
2376
2377    /// Returns the unsupported diagnostics collected during the most recent generate call.
2378    pub fn unsupported_messages(&self) -> &[String] {
2379        &self.unsupported_messages
2380    }
2381
2382    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2383        let message = message.into();
2384        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2385            return Err(crate::error::Error::generate(message));
2386        }
2387        self.unsupported_messages.push(message);
2388        Ok(())
2389    }
2390
2391    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2392        self.unsupported(message.to_string())?;
2393        self.write("/* ");
2394        self.write(message);
2395        self.write(" */");
2396        Ok(())
2397    }
2398
2399    fn format_unsupported_messages(&self) -> String {
2400        let limit = self.config.max_unsupported.max(1);
2401        if self.unsupported_messages.len() <= limit {
2402            return self.unsupported_messages.join("; ");
2403        }
2404
2405        let mut messages = self
2406            .unsupported_messages
2407            .iter()
2408            .take(limit)
2409            .cloned()
2410            .collect::<Vec<_>>();
2411        messages.push(format!(
2412            "... and {} more",
2413            self.unsupported_messages.len() - limit
2414        ));
2415        messages.join("; ")
2416    }
2417
2418    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2419    ///
2420    /// This is a static helper that creates a throwaway `Generator` internally.
2421    /// For repeated generation, prefer constructing a `Generator` once and calling
2422    /// [`generate`](Self::generate) on it.
2423    pub fn sql(expr: &Expression) -> Result<String> {
2424        let mut gen = Generator::new();
2425        gen.generate(expr)
2426    }
2427
2428    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2429    ///
2430    /// Produces human-readable output with newlines and indentation. A trailing
2431    /// semicolon is appended automatically if not already present.
2432    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2433        let config = GeneratorConfig {
2434            pretty: true,
2435            ..Default::default()
2436        };
2437        let mut gen = Generator::with_config(config);
2438        let mut sql = gen.generate(expr)?;
2439        // Add semicolon for pretty output
2440        if !sql.ends_with(';') {
2441            sql.push(';');
2442        }
2443        Ok(sql)
2444    }
2445
2446    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2447        #[cfg(feature = "stacker")]
2448        {
2449            let red_zone = if cfg!(debug_assertions) {
2450                4 * 1024 * 1024
2451            } else {
2452                1024 * 1024
2453            };
2454            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2455                self.generate_expression_inner(expr)
2456            })
2457        }
2458        #[cfg(not(feature = "stacker"))]
2459        {
2460            self.generate_expression_inner(expr)
2461        }
2462    }
2463
2464    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2465        match expr {
2466            Expression::Select(select) => self.generate_select(select),
2467            Expression::Union(union) => self.generate_union(union),
2468            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2469            Expression::Except(except) => self.generate_except(except),
2470            Expression::Insert(insert) => self.generate_insert(insert),
2471            Expression::Update(update) => self.generate_update(update),
2472            Expression::Delete(delete) => self.generate_delete(delete),
2473            Expression::Literal(lit) => self.generate_literal(lit),
2474            Expression::Boolean(b) => self.generate_boolean(b),
2475            Expression::Null(_) => {
2476                self.write_keyword("NULL");
2477                Ok(())
2478            }
2479            Expression::Identifier(id) => self.generate_identifier(id),
2480            Expression::Column(col) => self.generate_column(col),
2481            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2482            Expression::Connect(c) => self.generate_connect_expr(c),
2483            Expression::Prior(p) => self.generate_prior(p),
2484            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2485            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2486            Expression::Table(table) => self.generate_table(table),
2487            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2488            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2489            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2490            Expression::Star(star) => self.generate_star(star),
2491            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2492            Expression::Alias(alias) => self.generate_alias(alias),
2493            Expression::Cast(cast) => self.generate_cast(cast),
2494            Expression::Collation(coll) => self.generate_collation(coll),
2495            Expression::Case(case) => self.generate_case(case),
2496            Expression::Function(func) => self.generate_function(func),
2497            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2498            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2499            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2500            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2501            Expression::Interval(interval) => self.generate_interval(interval),
2502
2503            // String functions
2504            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2505            Expression::Substring(f) => self.generate_substring(f),
2506            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2507            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2508            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2509            Expression::Trim(f) => self.generate_trim(f),
2510            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2511            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2512            Expression::Replace(f) => self.generate_replace(f),
2513            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2514            Expression::Left(f) => self.generate_left_right("LEFT", f),
2515            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2516            Expression::Repeat(f) => self.generate_repeat(f),
2517            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2518            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2519            Expression::Split(f) => self.generate_split(f),
2520            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2521            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2522            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2523            Expression::Overlay(f) => self.generate_overlay(f),
2524
2525            // Math functions
2526            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2527            Expression::Round(f) => self.generate_round(f),
2528            Expression::Floor(f) => self.generate_floor(f),
2529            Expression::Ceil(f) => self.generate_ceil(f),
2530            Expression::Power(f) => self.generate_power(f),
2531            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2532            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2533            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2534            Expression::Log(f) => self.generate_log(f),
2535            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2536            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2537            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2538            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2539
2540            // Date/time functions
2541            Expression::CurrentDate(_) => {
2542                self.write_keyword("CURRENT_DATE");
2543                Ok(())
2544            }
2545            Expression::CurrentTime(f) => self.generate_current_time(f),
2546            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2547            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2548            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2549            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2550            Expression::DateDiff(f) => self.generate_datediff(f),
2551            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2552            Expression::Extract(f) => self.generate_extract(f),
2553            Expression::ToDate(f) => self.generate_to_date(f),
2554            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2555
2556            // Control flow functions
2557            Expression::Coalesce(f) => {
2558                // Use original function name if preserved (COALESCE, IFNULL)
2559                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2560                self.generate_vararg_func(func_name, &f.expressions)
2561            }
2562            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2563            Expression::IfFunc(f) => self.generate_if_func(f),
2564            Expression::IfNull(f) => self.generate_ifnull(f),
2565            Expression::Nvl(f) => self.generate_nvl(f),
2566            Expression::Nvl2(f) => self.generate_nvl2(f),
2567
2568            // Type conversion
2569            Expression::TryCast(cast) => self.generate_try_cast(cast),
2570            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2571
2572            // Typed aggregate functions
2573            Expression::Count(f) => self.generate_count(f),
2574            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2575            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2576            Expression::Min(f) => self.generate_agg_func("MIN", f),
2577            Expression::Max(f) => self.generate_agg_func("MAX", f),
2578            Expression::GroupConcat(f) => self.generate_group_concat(f),
2579            Expression::StringAgg(f) => self.generate_string_agg(f),
2580            Expression::ListAgg(f) => self.generate_listagg(f),
2581            Expression::ArrayAgg(f) => {
2582                // Allow cross-dialect transforms to override the function name
2583                // (e.g., COLLECT_LIST for Spark)
2584                let override_name = f
2585                    .name
2586                    .as_ref()
2587                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2588                    .map(|n| n.to_ascii_uppercase());
2589                match override_name {
2590                    Some(name) => self.generate_agg_func(&name, f),
2591                    None => self.generate_agg_func("ARRAY_AGG", f),
2592                }
2593            }
2594            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2595            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2596            Expression::SumIf(f) => self.generate_sum_if(f),
2597            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2598            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2599            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2600            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2601            Expression::VarPop(f) => {
2602                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2603                    "VARIANCE_POP"
2604                } else {
2605                    "VAR_POP"
2606                };
2607                self.generate_agg_func(name, f)
2608            }
2609            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2610            Expression::Skewness(f) => {
2611                let name = match self.config.dialect {
2612                    Some(DialectType::Snowflake) => "SKEW",
2613                    _ => "SKEWNESS",
2614                };
2615                self.generate_agg_func(name, f)
2616            }
2617            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2618            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2619            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2620            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2621            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2622            Expression::ApproxDistinct(f) => {
2623                match self.config.dialect {
2624                    Some(DialectType::Hive)
2625                    | Some(DialectType::Spark)
2626                    | Some(DialectType::Databricks)
2627                    | Some(DialectType::BigQuery) => {
2628                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2629                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2630                    }
2631                    Some(DialectType::Redshift) => {
2632                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2633                        self.write_keyword("APPROXIMATE COUNT");
2634                        self.write("(");
2635                        self.write_keyword("DISTINCT");
2636                        self.write(" ");
2637                        self.generate_expression(&f.this)?;
2638                        self.write(")");
2639                        Ok(())
2640                    }
2641                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2642                }
2643            }
2644            Expression::ApproxCountDistinct(f) => {
2645                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2646            }
2647            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2648            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2649            Expression::LogicalAnd(f) => {
2650                let name = match self.config.dialect {
2651                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2652                    Some(DialectType::Spark)
2653                    | Some(DialectType::Databricks)
2654                    | Some(DialectType::PostgreSQL)
2655                    | Some(DialectType::DuckDB)
2656                    | Some(DialectType::Redshift) => "BOOL_AND",
2657                    Some(DialectType::Oracle)
2658                    | Some(DialectType::SQLite)
2659                    | Some(DialectType::MySQL) => "MIN",
2660                    _ => "BOOL_AND",
2661                };
2662                self.generate_agg_func(name, f)
2663            }
2664            Expression::LogicalOr(f) => {
2665                let name = match self.config.dialect {
2666                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2667                    Some(DialectType::Spark)
2668                    | Some(DialectType::Databricks)
2669                    | Some(DialectType::PostgreSQL)
2670                    | Some(DialectType::DuckDB)
2671                    | Some(DialectType::Redshift) => "BOOL_OR",
2672                    Some(DialectType::Oracle)
2673                    | Some(DialectType::SQLite)
2674                    | Some(DialectType::MySQL) => "MAX",
2675                    _ => "BOOL_OR",
2676                };
2677                self.generate_agg_func(name, f)
2678            }
2679
2680            // Typed window functions
2681            Expression::RowNumber(_) => {
2682                if self.config.dialect == Some(DialectType::ClickHouse) {
2683                    self.write("row_number");
2684                } else {
2685                    self.write_keyword("ROW_NUMBER");
2686                }
2687                self.write("()");
2688                Ok(())
2689            }
2690            Expression::Rank(r) => {
2691                self.write_keyword("RANK");
2692                self.write("(");
2693                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2694                if !r.args.is_empty() {
2695                    for (i, arg) in r.args.iter().enumerate() {
2696                        if i > 0 {
2697                            self.write(", ");
2698                        }
2699                        self.generate_expression(arg)?;
2700                    }
2701                } else if let Some(order_by) = &r.order_by {
2702                    // DuckDB: RANK(ORDER BY col)
2703                    self.write_keyword(" ORDER BY ");
2704                    for (i, ob) in order_by.iter().enumerate() {
2705                        if i > 0 {
2706                            self.write(", ");
2707                        }
2708                        self.generate_ordered(ob)?;
2709                    }
2710                }
2711                self.write(")");
2712                Ok(())
2713            }
2714            Expression::DenseRank(dr) => {
2715                self.write_keyword("DENSE_RANK");
2716                self.write("(");
2717                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2718                for (i, arg) in dr.args.iter().enumerate() {
2719                    if i > 0 {
2720                        self.write(", ");
2721                    }
2722                    self.generate_expression(arg)?;
2723                }
2724                self.write(")");
2725                Ok(())
2726            }
2727            Expression::NTile(f) => self.generate_ntile(f),
2728            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2729            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2730            Expression::FirstValue(f) => {
2731                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2732            }
2733            Expression::LastValue(f) => {
2734                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2735            }
2736            Expression::NthValue(f) => self.generate_nth_value(f),
2737            Expression::PercentRank(pr) => {
2738                self.write_keyword("PERCENT_RANK");
2739                self.write("(");
2740                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2741                if !pr.args.is_empty() {
2742                    for (i, arg) in pr.args.iter().enumerate() {
2743                        if i > 0 {
2744                            self.write(", ");
2745                        }
2746                        self.generate_expression(arg)?;
2747                    }
2748                } else if let Some(order_by) = &pr.order_by {
2749                    // DuckDB: PERCENT_RANK(ORDER BY col)
2750                    self.write_keyword(" ORDER BY ");
2751                    for (i, ob) in order_by.iter().enumerate() {
2752                        if i > 0 {
2753                            self.write(", ");
2754                        }
2755                        self.generate_ordered(ob)?;
2756                    }
2757                }
2758                self.write(")");
2759                Ok(())
2760            }
2761            Expression::CumeDist(cd) => {
2762                self.write_keyword("CUME_DIST");
2763                self.write("(");
2764                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2765                if !cd.args.is_empty() {
2766                    for (i, arg) in cd.args.iter().enumerate() {
2767                        if i > 0 {
2768                            self.write(", ");
2769                        }
2770                        self.generate_expression(arg)?;
2771                    }
2772                } else if let Some(order_by) = &cd.order_by {
2773                    // DuckDB: CUME_DIST(ORDER BY col)
2774                    self.write_keyword(" ORDER BY ");
2775                    for (i, ob) in order_by.iter().enumerate() {
2776                        if i > 0 {
2777                            self.write(", ");
2778                        }
2779                        self.generate_ordered(ob)?;
2780                    }
2781                }
2782                self.write(")");
2783                Ok(())
2784            }
2785            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2786            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2787
2788            // Additional string functions
2789            Expression::Contains(f) => {
2790                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2791            }
2792            Expression::StartsWith(f) => {
2793                let name = match self.config.dialect {
2794                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2795                    _ => "STARTS_WITH",
2796                };
2797                self.generate_binary_func(name, &f.this, &f.expression)
2798            }
2799            Expression::EndsWith(f) => {
2800                let name = match self.config.dialect {
2801                    Some(DialectType::Snowflake) => "ENDSWITH",
2802                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2803                    Some(DialectType::ClickHouse) => "endsWith",
2804                    _ => "ENDS_WITH",
2805                };
2806                self.generate_binary_func(name, &f.this, &f.expression)
2807            }
2808            Expression::Position(f) => self.generate_position(f),
2809            Expression::Initcap(f) => match self.config.dialect {
2810                Some(DialectType::Presto)
2811                | Some(DialectType::Trino)
2812                | Some(DialectType::Athena) => {
2813                    self.write_keyword("REGEXP_REPLACE");
2814                    self.write("(");
2815                    self.generate_expression(&f.this)?;
2816                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2817                    Ok(())
2818                }
2819                _ => self.generate_simple_func("INITCAP", &f.this),
2820            },
2821            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2822            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2823            Expression::CharFunc(f) => self.generate_char_func(f),
2824            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2825            Expression::Levenshtein(f) => {
2826                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2827            }
2828
2829            // Additional math functions
2830            Expression::ModFunc(f) => self.generate_mod_func(f),
2831            Expression::Random(_) => {
2832                self.write_keyword("RANDOM");
2833                self.write("()");
2834                Ok(())
2835            }
2836            Expression::Rand(f) => self.generate_rand(f),
2837            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2838            Expression::Pi(_) => {
2839                self.write_keyword("PI");
2840                self.write("()");
2841                Ok(())
2842            }
2843            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2844            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2845            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2846            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2847            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2848            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2849            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2850            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2851            Expression::Atan2(f) => {
2852                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2853                self.generate_binary_func(name, &f.this, &f.expression)
2854            }
2855
2856            // Control flow
2857            Expression::Decode(f) => self.generate_decode(f),
2858
2859            // Additional date/time functions
2860            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2861            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2862            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2863            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2864            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2865            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2866            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2867            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2868            Expression::DayOfWeek(f) => {
2869                let name = match self.config.dialect {
2870                    Some(DialectType::Presto)
2871                    | Some(DialectType::Trino)
2872                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2873                    Some(DialectType::DuckDB) => "ISODOW",
2874                    _ => "DAYOFWEEK",
2875                };
2876                self.generate_simple_func(name, &f.this)
2877            }
2878            Expression::DayOfMonth(f) => {
2879                let name = match self.config.dialect {
2880                    Some(DialectType::Presto)
2881                    | Some(DialectType::Trino)
2882                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2883                    _ => "DAYOFMONTH",
2884                };
2885                self.generate_simple_func(name, &f.this)
2886            }
2887            Expression::DayOfYear(f) => {
2888                let name = match self.config.dialect {
2889                    Some(DialectType::Presto)
2890                    | Some(DialectType::Trino)
2891                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2892                    _ => "DAYOFYEAR",
2893                };
2894                self.generate_simple_func(name, &f.this)
2895            }
2896            Expression::WeekOfYear(f) => {
2897                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2898                let name = match self.config.dialect {
2899                    Some(DialectType::Hive)
2900                    | Some(DialectType::DuckDB)
2901                    | Some(DialectType::Spark)
2902                    | Some(DialectType::Databricks)
2903                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2904                    _ => "WEEK_OF_YEAR",
2905                };
2906                self.generate_simple_func(name, &f.this)
2907            }
2908            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2909            Expression::AddMonths(f) => {
2910                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2911            }
2912            Expression::MonthsBetween(f) => {
2913                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2914            }
2915            Expression::LastDay(f) => self.generate_last_day(f),
2916            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2917            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2918            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2919            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2920            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2921            Expression::MakeDate(f) => self.generate_make_date(f),
2922            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2923            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2924
2925            // Array functions
2926            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2927            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2928            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2929            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2930            Expression::ArrayContains(f) => {
2931                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2932            }
2933            Expression::ArrayPosition(f) => {
2934                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2935            }
2936            Expression::ArrayAppend(f) => {
2937                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2938            }
2939            Expression::ArrayPrepend(f) => {
2940                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2941            }
2942            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2943            Expression::ArraySort(f) => self.generate_array_sort(f),
2944            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2945            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2946            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2947            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2948            Expression::Unnest(f) => self.generate_unnest(f),
2949            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2950            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2951            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2952            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2953            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2954            Expression::ArrayCompact(f) => {
2955                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2956                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2957                    self.write("LIST_FILTER(");
2958                    self.generate_expression(&f.this)?;
2959                    self.write(", _u -> NOT _u IS NULL)");
2960                    Ok(())
2961                } else {
2962                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2963                }
2964            }
2965            Expression::ArrayIntersect(f) => {
2966                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2967                self.generate_vararg_func(func_name, &f.expressions)
2968            }
2969            Expression::ArrayUnion(f) => {
2970                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2971            }
2972            Expression::ArrayExcept(f) => {
2973                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2974            }
2975            Expression::ArrayRemove(f) => {
2976                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2977            }
2978            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2979            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2980            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2981
2982            // Struct functions
2983            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2984            Expression::StructExtract(f) => self.generate_struct_extract(f),
2985            Expression::NamedStruct(f) => self.generate_named_struct(f),
2986
2987            // Map functions
2988            Expression::MapFunc(f) => self.generate_map_constructor(f),
2989            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2990            Expression::MapFromArrays(f) => {
2991                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2992            }
2993            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2994            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2995            Expression::MapContainsKey(f) => {
2996                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2997            }
2998            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2999            Expression::ElementAt(f) => {
3000                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3001            }
3002            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3003            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3004
3005            // JSON functions
3006            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3007            Expression::JsonExtractScalar(f) => {
3008                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3009            }
3010            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3011            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3012            Expression::JsonObject(f) => self.generate_json_object(f),
3013            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3014            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3015            Expression::JsonArrayLength(f) => {
3016                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3017            }
3018            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3019            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3020            Expression::ParseJson(f) => {
3021                let name = match self.config.dialect {
3022                    Some(DialectType::Presto)
3023                    | Some(DialectType::Trino)
3024                    | Some(DialectType::Athena) => "JSON_PARSE",
3025                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3026                        // PostgreSQL: CAST(x AS JSON)
3027                        self.write_keyword("CAST");
3028                        self.write("(");
3029                        self.generate_expression(&f.this)?;
3030                        self.write_keyword(" AS ");
3031                        self.write_keyword("JSON");
3032                        self.write(")");
3033                        return Ok(());
3034                    }
3035                    Some(DialectType::Hive)
3036                    | Some(DialectType::Spark)
3037                    | Some(DialectType::MySQL)
3038                    | Some(DialectType::SingleStore)
3039                    | Some(DialectType::TiDB)
3040                    | Some(DialectType::TSQL) => {
3041                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3042                        self.generate_expression(&f.this)?;
3043                        return Ok(());
3044                    }
3045                    Some(DialectType::DuckDB) => "JSON",
3046                    _ => "PARSE_JSON",
3047                };
3048                self.generate_simple_func(name, &f.this)
3049            }
3050            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3051            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3052            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3053            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3054            Expression::JsonMergePatch(f) => {
3055                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3056            }
3057            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3058            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3059
3060            // Type casting/conversion
3061            Expression::Convert(f) => self.generate_convert(f),
3062            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3063
3064            // Additional expressions
3065            Expression::Lambda(f) => self.generate_lambda(f),
3066            Expression::Parameter(f) => self.generate_parameter(f),
3067            Expression::Placeholder(f) => self.generate_placeholder(f),
3068            Expression::NamedArgument(f) => self.generate_named_argument(f),
3069            Expression::TableArgument(f) => self.generate_table_argument(f),
3070            Expression::SqlComment(f) => self.generate_sql_comment(f),
3071
3072            // Additional predicates
3073            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3074            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3075            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3076            Expression::SimilarTo(f) => self.generate_similar_to(f),
3077            Expression::Any(f) => self.generate_quantified("ANY", f),
3078            Expression::All(f) => self.generate_quantified("ALL", f),
3079            Expression::Overlaps(f) => self.generate_overlaps(f),
3080
3081            // Bitwise operations
3082            Expression::BitwiseLeftShift(op) => {
3083                if matches!(
3084                    self.config.dialect,
3085                    Some(DialectType::Presto) | Some(DialectType::Trino)
3086                ) {
3087                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3088                    self.write("(");
3089                    self.generate_expression(&op.left)?;
3090                    self.write(", ");
3091                    self.generate_expression(&op.right)?;
3092                    self.write(")");
3093                    Ok(())
3094                } else if matches!(
3095                    self.config.dialect,
3096                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3097                ) {
3098                    self.write_keyword("SHIFTLEFT");
3099                    self.write("(");
3100                    self.generate_expression(&op.left)?;
3101                    self.write(", ");
3102                    self.generate_expression(&op.right)?;
3103                    self.write(")");
3104                    Ok(())
3105                } else {
3106                    self.generate_binary_op(op, "<<")
3107                }
3108            }
3109            Expression::BitwiseRightShift(op) => {
3110                if matches!(
3111                    self.config.dialect,
3112                    Some(DialectType::Presto) | Some(DialectType::Trino)
3113                ) {
3114                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3115                    self.write("(");
3116                    self.generate_expression(&op.left)?;
3117                    self.write(", ");
3118                    self.generate_expression(&op.right)?;
3119                    self.write(")");
3120                    Ok(())
3121                } else if matches!(
3122                    self.config.dialect,
3123                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3124                ) {
3125                    self.write_keyword("SHIFTRIGHT");
3126                    self.write("(");
3127                    self.generate_expression(&op.left)?;
3128                    self.write(", ");
3129                    self.generate_expression(&op.right)?;
3130                    self.write(")");
3131                    Ok(())
3132                } else {
3133                    self.generate_binary_op(op, ">>")
3134                }
3135            }
3136            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3137            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3138            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3139
3140            // Array/struct/map access
3141            Expression::Subscript(s) => self.generate_subscript(s),
3142            Expression::Dot(d) => self.generate_dot_access(d),
3143            Expression::MethodCall(m) => self.generate_method_call(m),
3144            Expression::ArraySlice(s) => self.generate_array_slice(s),
3145
3146            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3147            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3148            Expression::Add(op) => self.generate_binary_op(op, "+"),
3149            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3150            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3151            Expression::Div(op) => self.generate_binary_op(op, "/"),
3152            Expression::IntDiv(f) => {
3153                use crate::dialects::DialectType;
3154                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3155                    // DuckDB uses // operator for integer division
3156                    self.generate_expression(&f.this)?;
3157                    self.write(" // ");
3158                    self.generate_expression(&f.expression)?;
3159                    Ok(())
3160                } else if matches!(
3161                    self.config.dialect,
3162                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3163                ) {
3164                    // Hive/Spark use DIV as an infix operator
3165                    self.generate_expression(&f.this)?;
3166                    self.write(" ");
3167                    self.write_keyword("DIV");
3168                    self.write(" ");
3169                    self.generate_expression(&f.expression)?;
3170                    Ok(())
3171                } else {
3172                    // Other dialects use DIV function
3173                    self.write_keyword("DIV");
3174                    self.write("(");
3175                    self.generate_expression(&f.this)?;
3176                    self.write(", ");
3177                    self.generate_expression(&f.expression)?;
3178                    self.write(")");
3179                    Ok(())
3180                }
3181            }
3182            Expression::Mod(op) => {
3183                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3184                    self.generate_binary_op(op, "MOD")
3185                } else {
3186                    self.generate_binary_op(op, "%")
3187                }
3188            }
3189            Expression::Eq(op) => self.generate_binary_op(op, "="),
3190            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3191            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3192            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3193            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3194            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3195            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3196            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3197            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3198            Expression::Concat(op) => {
3199                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3200                if self.config.dialect == Some(DialectType::Solr) {
3201                    self.generate_binary_op(op, "OR")
3202                } else if self.config.dialect == Some(DialectType::MySQL) {
3203                    self.generate_mysql_concat_from_concat(op)
3204                } else {
3205                    self.generate_binary_op(op, "||")
3206                }
3207            }
3208            Expression::BitwiseAnd(op) => {
3209                // Presto/Trino use BITWISE_AND function
3210                if matches!(
3211                    self.config.dialect,
3212                    Some(DialectType::Presto) | Some(DialectType::Trino)
3213                ) {
3214                    self.write_keyword("BITWISE_AND");
3215                    self.write("(");
3216                    self.generate_expression(&op.left)?;
3217                    self.write(", ");
3218                    self.generate_expression(&op.right)?;
3219                    self.write(")");
3220                    Ok(())
3221                } else {
3222                    self.generate_binary_op(op, "&")
3223                }
3224            }
3225            Expression::BitwiseOr(op) => {
3226                // Presto/Trino use BITWISE_OR function
3227                if matches!(
3228                    self.config.dialect,
3229                    Some(DialectType::Presto) | Some(DialectType::Trino)
3230                ) {
3231                    self.write_keyword("BITWISE_OR");
3232                    self.write("(");
3233                    self.generate_expression(&op.left)?;
3234                    self.write(", ");
3235                    self.generate_expression(&op.right)?;
3236                    self.write(")");
3237                    Ok(())
3238                } else {
3239                    self.generate_binary_op(op, "|")
3240                }
3241            }
3242            Expression::BitwiseXor(op) => {
3243                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3244                if matches!(
3245                    self.config.dialect,
3246                    Some(DialectType::Presto) | Some(DialectType::Trino)
3247                ) {
3248                    self.write_keyword("BITWISE_XOR");
3249                    self.write("(");
3250                    self.generate_expression(&op.left)?;
3251                    self.write(", ");
3252                    self.generate_expression(&op.right)?;
3253                    self.write(")");
3254                    Ok(())
3255                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3256                    self.generate_binary_op(op, "#")
3257                } else {
3258                    self.generate_binary_op(op, "^")
3259                }
3260            }
3261            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3262            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3263            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3264            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3265            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3266            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3267            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3268            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3269            Expression::JSONBContains(f) => {
3270                // PostgreSQL JSONB contains key operator: a ? b
3271                self.generate_expression(&f.this)?;
3272                self.write_space();
3273                self.write("?");
3274                self.write_space();
3275                self.generate_expression(&f.expression)
3276            }
3277            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3278            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3279            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3280            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3281            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3282            Expression::BitwiseNot(op) => {
3283                // Presto/Trino use BITWISE_NOT function
3284                if matches!(
3285                    self.config.dialect,
3286                    Some(DialectType::Presto) | Some(DialectType::Trino)
3287                ) {
3288                    self.write_keyword("BITWISE_NOT");
3289                    self.write("(");
3290                    self.generate_expression(&op.this)?;
3291                    self.write(")");
3292                    Ok(())
3293                } else {
3294                    self.generate_unary_op(op, "~")
3295                }
3296            }
3297            Expression::In(in_expr) => self.generate_in(in_expr),
3298            Expression::Between(between) => self.generate_between(between),
3299            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3300            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3301            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3302            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3303            Expression::Is(is_expr) => self.generate_is(is_expr),
3304            Expression::Exists(exists) => self.generate_exists(exists),
3305            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3306            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3307            Expression::Paren(paren) => {
3308                // JoinedTable already outputs its own parentheses, so don't double-wrap
3309                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3310
3311                if !skip_parens {
3312                    self.write("(");
3313                    if self.config.pretty {
3314                        self.write_newline();
3315                        self.indent_level += 1;
3316                        self.write_indent();
3317                    }
3318                }
3319                self.generate_expression(&paren.this)?;
3320                if !skip_parens {
3321                    if self.config.pretty {
3322                        self.write_newline();
3323                        self.indent_level -= 1;
3324                        self.write_indent();
3325                    }
3326                    self.write(")");
3327                }
3328                // Output trailing comments after closing paren
3329                for comment in &paren.trailing_comments {
3330                    self.write(" ");
3331                    self.write_formatted_comment(comment);
3332                }
3333                Ok(())
3334            }
3335            Expression::Array(arr) => self.generate_array(arr),
3336            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3337            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3338            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3339            Expression::DataType(dt) => self.generate_data_type(dt),
3340            Expression::Raw(raw) => {
3341                self.write(&raw.sql);
3342                Ok(())
3343            }
3344            Expression::CreateTask(task) => self.generate_create_task(task),
3345            Expression::Command(cmd) => {
3346                self.write(&cmd.this);
3347                Ok(())
3348            }
3349            Expression::Kill(kill) => {
3350                self.write_keyword("KILL");
3351                if let Some(kind) = &kill.kind {
3352                    self.write_space();
3353                    self.write_keyword(kind);
3354                }
3355                self.write_space();
3356                self.generate_expression(&kill.this)?;
3357                Ok(())
3358            }
3359            Expression::Execute(exec) => {
3360                self.write_keyword("EXECUTE");
3361                self.write_space();
3362                self.generate_expression(&exec.this)?;
3363                for (i, param) in exec.parameters.iter().enumerate() {
3364                    if i == 0 {
3365                        self.write_space();
3366                    } else {
3367                        self.write(", ");
3368                    }
3369                    self.write(&param.name);
3370                    // Only write = value for named parameters (not positional)
3371                    if !param.positional {
3372                        self.write(" = ");
3373                        self.generate_expression(&param.value)?;
3374                    }
3375                    if param.output {
3376                        self.write_space();
3377                        self.write_keyword("OUTPUT");
3378                    }
3379                }
3380                if let Some(ref suffix) = exec.suffix {
3381                    self.write_space();
3382                    self.write(suffix);
3383                }
3384                Ok(())
3385            }
3386            Expression::Annotated(annotated) => {
3387                self.generate_expression(&annotated.this)?;
3388                for comment in &annotated.trailing_comments {
3389                    self.write(" ");
3390                    self.write_formatted_comment(comment);
3391                }
3392                Ok(())
3393            }
3394
3395            // DDL statements
3396            Expression::CreateTable(ct) => self.generate_create_table(ct),
3397            Expression::DropTable(dt) => self.generate_drop_table(dt),
3398            Expression::Undrop(u) => self.generate_undrop(u),
3399            Expression::AlterTable(at) => self.generate_alter_table(at),
3400            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3401            Expression::DropIndex(di) => self.generate_drop_index(di),
3402            Expression::CreateView(cv) => self.generate_create_view(cv),
3403            Expression::DropView(dv) => self.generate_drop_view(dv),
3404            Expression::AlterView(av) => self.generate_alter_view(av),
3405            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3406            Expression::Truncate(tr) => self.generate_truncate(tr),
3407            Expression::Use(u) => self.generate_use(u),
3408            // Phase 4: Additional DDL statements
3409            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3410            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3411            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3412            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3413            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3414            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3415            Expression::DropFunction(df) => self.generate_drop_function(df),
3416            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3417            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3418            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3419            Expression::CreateSynonym(cs) => {
3420                self.write_keyword("CREATE SYNONYM");
3421                self.write_space();
3422                self.generate_table(&cs.name)?;
3423                self.write_space();
3424                self.write_keyword("FOR");
3425                self.write_space();
3426                self.generate_table(&cs.target)?;
3427                Ok(())
3428            }
3429            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3430            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3431            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3432            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3433            Expression::CreateType(ct) => self.generate_create_type(ct),
3434            Expression::DropType(dt) => self.generate_drop_type(dt),
3435            Expression::Describe(d) => self.generate_describe(d),
3436            Expression::Show(s) => self.generate_show(s),
3437
3438            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3439            Expression::Cache(c) => self.generate_cache(c),
3440            Expression::Uncache(u) => self.generate_uncache(u),
3441            Expression::LoadData(l) => self.generate_load_data(l),
3442            Expression::Pragma(p) => self.generate_pragma(p),
3443            Expression::Grant(g) => self.generate_grant(g),
3444            Expression::Revoke(r) => self.generate_revoke(r),
3445            Expression::Comment(c) => self.generate_comment(c),
3446            Expression::SetStatement(s) => self.generate_set_statement(s),
3447
3448            // PIVOT/UNPIVOT
3449            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3450            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3451
3452            // VALUES table constructor
3453            Expression::Values(values) => self.generate_values(values),
3454
3455            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3456            Expression::AIAgg(e) => self.generate_ai_agg(e),
3457            Expression::AIClassify(e) => self.generate_ai_classify(e),
3458            Expression::AddPartition(e) => self.generate_add_partition(e),
3459            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3460            Expression::Aliases(e) => self.generate_aliases(e),
3461            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3462            Expression::AlterColumn(e) => self.generate_alter_column(e),
3463            Expression::AlterSession(e) => self.generate_alter_session(e),
3464            Expression::AlterSet(e) => self.generate_alter_set(e),
3465            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3466            Expression::Analyze(e) => self.generate_analyze(e),
3467            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3468            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3469            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3470            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3471            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3472            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3473            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3474            Expression::Anonymous(e) => self.generate_anonymous(e),
3475            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3476            Expression::Apply(e) => self.generate_apply(e),
3477            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3478            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3479            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3480            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3481            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3482            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3483            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3484            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3485            Expression::ArgMax(e) => self.generate_arg_max(e),
3486            Expression::ArgMin(e) => self.generate_arg_min(e),
3487            Expression::ArrayAll(e) => self.generate_array_all(e),
3488            Expression::ArrayAny(e) => self.generate_array_any(e),
3489            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3490            Expression::ArraySum(e) => self.generate_array_sum(e),
3491            Expression::AtIndex(e) => self.generate_at_index(e),
3492            Expression::Attach(e) => self.generate_attach(e),
3493            Expression::AttachOption(e) => self.generate_attach_option(e),
3494            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3495            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3496            Expression::BackupProperty(e) => self.generate_backup_property(e),
3497            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3498            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3499            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3500            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3501            Expression::Booland(e) => self.generate_booland(e),
3502            Expression::Boolor(e) => self.generate_boolor(e),
3503            Expression::BuildProperty(e) => self.generate_build_property(e),
3504            Expression::ByteString(e) => self.generate_byte_string(e),
3505            Expression::CaseSpecificColumnConstraint(e) => {
3506                self.generate_case_specific_column_constraint(e)
3507            }
3508            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3509            Expression::Changes(e) => self.generate_changes(e),
3510            Expression::CharacterSetColumnConstraint(e) => {
3511                self.generate_character_set_column_constraint(e)
3512            }
3513            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3514            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3515            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3516            Expression::CheckJson(e) => self.generate_check_json(e),
3517            Expression::CheckXml(e) => self.generate_check_xml(e),
3518            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3519            Expression::Clone(e) => self.generate_clone(e),
3520            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3521            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3522            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3523            Expression::CollateProperty(e) => self.generate_collate_property(e),
3524            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3525            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3526            Expression::ColumnPosition(e) => self.generate_column_position(e),
3527            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3528            Expression::Columns(e) => self.generate_columns(e),
3529            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3530            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3531            Expression::Commit(e) => self.generate_commit(e),
3532            Expression::Comprehension(e) => self.generate_comprehension(e),
3533            Expression::Compress(e) => self.generate_compress(e),
3534            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3535            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3536            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3537            Expression::Constraint(e) => self.generate_constraint(e),
3538            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3539            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3540            Expression::Copy(e) => self.generate_copy(e),
3541            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3542            Expression::Corr(e) => self.generate_corr(e),
3543            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3544            Expression::CovarPop(e) => self.generate_covar_pop(e),
3545            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3546            Expression::Credentials(e) => self.generate_credentials(e),
3547            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3548            Expression::Cte(e) => self.generate_cte(e),
3549            Expression::Cube(e) => self.generate_cube(e),
3550            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3551            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3552            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3553            Expression::CurrentUser(e) => self.generate_current_user(e),
3554            Expression::DPipe(e) => self.generate_d_pipe(e),
3555            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3556            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3557            Expression::Date(e) => self.generate_date_func(e),
3558            Expression::DateBin(e) => self.generate_date_bin(e),
3559            Expression::DateFormatColumnConstraint(e) => {
3560                self.generate_date_format_column_constraint(e)
3561            }
3562            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3563            Expression::Datetime(e) => self.generate_datetime(e),
3564            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3565            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3566            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3567            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3568            Expression::Dayname(e) => self.generate_dayname(e),
3569            Expression::Declare(e) => self.generate_declare(e),
3570            Expression::DeclareItem(e) => self.generate_declare_item(e),
3571            Expression::DecodeCase(e) => self.generate_decode_case(e),
3572            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3573            Expression::DecompressString(e) => self.generate_decompress_string(e),
3574            Expression::Decrypt(e) => self.generate_decrypt(e),
3575            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3576            Expression::DefaultColumnConstraint(e) => {
3577                self.write_keyword("DEFAULT");
3578                self.write_space();
3579                self.generate_expression(&e.this)?;
3580                if let Some(ref col) = e.for_column {
3581                    self.write_space();
3582                    self.write_keyword("FOR");
3583                    self.write_space();
3584                    self.generate_identifier(col)?;
3585                }
3586                Ok(())
3587            }
3588            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3589            Expression::Detach(e) => self.generate_detach(e),
3590            Expression::DictProperty(e) => self.generate_dict_property(e),
3591            Expression::DictRange(e) => self.generate_dict_range(e),
3592            Expression::Directory(e) => self.generate_directory(e),
3593            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3594            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3595            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3596            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3597            Expression::DotProduct(e) => self.generate_dot_product(e),
3598            Expression::DropPartition(e) => self.generate_drop_partition(e),
3599            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3600            Expression::Elt(e) => self.generate_elt(e),
3601            Expression::Encode(e) => self.generate_encode(e),
3602            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3603            Expression::Encrypt(e) => self.generate_encrypt(e),
3604            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3605            Expression::EngineProperty(e) => self.generate_engine_property(e),
3606            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3607            Expression::EphemeralColumnConstraint(e) => {
3608                self.generate_ephemeral_column_constraint(e)
3609            }
3610            Expression::EqualNull(e) => self.generate_equal_null(e),
3611            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3612            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3613            Expression::Export(e) => self.generate_export(e),
3614            Expression::ExternalProperty(e) => self.generate_external_property(e),
3615            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3616            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3617            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3618            Expression::Fetch(e) => self.generate_fetch(e),
3619            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3620            Expression::Filter(e) => self.generate_filter(e),
3621            Expression::Float64(e) => self.generate_float64(e),
3622            Expression::ForIn(e) => self.generate_for_in(e),
3623            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3624            Expression::Format(e) => self.generate_format(e),
3625            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3626            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3627            Expression::From(e) => self.generate_from(e),
3628            Expression::FromBase(e) => self.generate_from_base(e),
3629            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3630            Expression::GapFill(e) => self.generate_gap_fill(e),
3631            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3632            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3633            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3634            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3635            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3636                self.generate_generated_as_identity_column_constraint(e)
3637            }
3638            Expression::GeneratedAsRowColumnConstraint(e) => {
3639                self.generate_generated_as_row_column_constraint(e)
3640            }
3641            Expression::Get(e) => self.generate_get(e),
3642            Expression::GetExtract(e) => self.generate_get_extract(e),
3643            Expression::Getbit(e) => self.generate_getbit(e),
3644            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3645            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3646            Expression::Group(e) => self.generate_group(e),
3647            Expression::GroupBy(e) => self.generate_group_by(e),
3648            Expression::Grouping(e) => self.generate_grouping(e),
3649            Expression::GroupingId(e) => self.generate_grouping_id(e),
3650            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3651            Expression::HashAgg(e) => self.generate_hash_agg(e),
3652            Expression::Having(e) => self.generate_having(e),
3653            Expression::HavingMax(e) => self.generate_having_max(e),
3654            Expression::Heredoc(e) => self.generate_heredoc(e),
3655            Expression::HexEncode(e) => self.generate_hex_encode(e),
3656            Expression::Hll(e) => self.generate_hll(e),
3657            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3658            Expression::IncludeProperty(e) => self.generate_include_property(e),
3659            Expression::Index(e) => self.generate_index(e),
3660            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3661            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3662            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3663            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3664            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3665            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3666            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3667            Expression::Install(e) => self.generate_install(e),
3668            Expression::IntervalOp(e) => self.generate_interval_op(e),
3669            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3670            Expression::IntoClause(e) => self.generate_into_clause(e),
3671            Expression::Introducer(e) => self.generate_introducer(e),
3672            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3673            Expression::JSON(e) => self.generate_json(e),
3674            Expression::JSONArray(e) => self.generate_json_array(e),
3675            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3676            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3677            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3678            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3679            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3680            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3681            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3682            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3683            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3684            Expression::JSONExists(e) => self.generate_json_exists(e),
3685            Expression::JSONCast(e) => self.generate_json_cast(e),
3686            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3687            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3688            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3689            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3690            Expression::JSONFormat(e) => self.generate_json_format(e),
3691            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3692            Expression::JSONKeys(e) => self.generate_json_keys(e),
3693            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3694            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3695            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3696            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3697            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3698            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3699            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3700            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3701            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3702            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3703            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3704            Expression::JSONRemove(e) => self.generate_json_remove(e),
3705            Expression::JSONSchema(e) => self.generate_json_schema(e),
3706            Expression::JSONSet(e) => self.generate_json_set(e),
3707            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3708            Expression::JSONTable(e) => self.generate_json_table(e),
3709            Expression::JSONType(e) => self.generate_json_type(e),
3710            Expression::JSONValue(e) => self.generate_json_value(e),
3711            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3712            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3713            Expression::JoinHint(e) => self.generate_join_hint(e),
3714            Expression::JournalProperty(e) => self.generate_journal_property(e),
3715            Expression::LanguageProperty(e) => self.generate_language_property(e),
3716            Expression::Lateral(e) => self.generate_lateral(e),
3717            Expression::LikeProperty(e) => self.generate_like_property(e),
3718            Expression::Limit(e) => self.generate_limit(e),
3719            Expression::LimitOptions(e) => self.generate_limit_options(e),
3720            Expression::List(e) => self.generate_list(e),
3721            Expression::ToMap(e) => self.generate_tomap(e),
3722            Expression::Localtime(e) => self.generate_localtime(e),
3723            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3724            Expression::LocationProperty(e) => self.generate_location_property(e),
3725            Expression::Lock(e) => self.generate_lock(e),
3726            Expression::LockProperty(e) => self.generate_lock_property(e),
3727            Expression::LockingProperty(e) => self.generate_locking_property(e),
3728            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3729            Expression::LogProperty(e) => self.generate_log_property(e),
3730            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3731            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3732            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3733            Expression::MakeInterval(e) => self.generate_make_interval(e),
3734            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3735            Expression::Map(e) => self.generate_map(e),
3736            Expression::MapCat(e) => self.generate_map_cat(e),
3737            Expression::MapDelete(e) => self.generate_map_delete(e),
3738            Expression::MapInsert(e) => self.generate_map_insert(e),
3739            Expression::MapPick(e) => self.generate_map_pick(e),
3740            Expression::MaskingPolicyColumnConstraint(e) => {
3741                self.generate_masking_policy_column_constraint(e)
3742            }
3743            Expression::MatchAgainst(e) => self.generate_match_against(e),
3744            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3745            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3746            Expression::Merge(e) => self.generate_merge(e),
3747            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3748            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3749            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3750            Expression::Minhash(e) => self.generate_minhash(e),
3751            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3752            Expression::Monthname(e) => self.generate_monthname(e),
3753            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3754            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3755            Expression::Normal(e) => self.generate_normal(e),
3756            Expression::Normalize(e) => self.generate_normalize(e),
3757            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3758            Expression::Nullif(e) => self.generate_nullif(e),
3759            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3760            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3761            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3762            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3763            Expression::Offset(e) => self.generate_offset(e),
3764            Expression::Qualify(e) => self.generate_qualify(e),
3765            Expression::OnCluster(e) => self.generate_on_cluster(e),
3766            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3767            Expression::OnCondition(e) => self.generate_on_condition(e),
3768            Expression::OnConflict(e) => self.generate_on_conflict(e),
3769            Expression::OnProperty(e) => self.generate_on_property(e),
3770            Expression::Opclass(e) => self.generate_opclass(e),
3771            Expression::OpenJSON(e) => self.generate_open_json(e),
3772            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3773            Expression::Operator(e) => self.generate_operator(e),
3774            Expression::OrderBy(e) => self.generate_order_by(e),
3775            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3776            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3777            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3778            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3779            Expression::ParseIp(e) => self.generate_parse_ip(e),
3780            Expression::ParseJSON(e) => self.generate_parse_json(e),
3781            Expression::ParseTime(e) => self.generate_parse_time(e),
3782            Expression::ParseUrl(e) => self.generate_parse_url(e),
3783            Expression::Partition(e) => self.generate_partition_expr(e),
3784            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3785            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3786            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3787            Expression::PartitionByRangePropertyDynamic(e) => {
3788                self.generate_partition_by_range_property_dynamic(e)
3789            }
3790            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3791            Expression::PartitionList(e) => self.generate_partition_list(e),
3792            Expression::PartitionRange(e) => self.generate_partition_range(e),
3793            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3794            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3795            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3796            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3797            Expression::PeriodForSystemTimeConstraint(e) => {
3798                self.generate_period_for_system_time_constraint(e)
3799            }
3800            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3801            Expression::PivotAny(e) => self.generate_pivot_any(e),
3802            Expression::Predict(e) => self.generate_predict(e),
3803            Expression::PreviousDay(e) => self.generate_previous_day(e),
3804            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3805            Expression::PrimaryKeyColumnConstraint(e) => {
3806                self.generate_primary_key_column_constraint(e)
3807            }
3808            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3809            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3810            Expression::OptionsProperty(e) => self.generate_options_property(e),
3811            Expression::Properties(e) => self.generate_properties(e),
3812            Expression::Property(e) => self.generate_property(e),
3813            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3814            Expression::Put(e) => self.generate_put(e),
3815            Expression::Quantile(e) => self.generate_quantile(e),
3816            Expression::QueryBand(e) => self.generate_query_band(e),
3817            Expression::QueryOption(e) => self.generate_query_option(e),
3818            Expression::QueryTransform(e) => self.generate_query_transform(e),
3819            Expression::Randn(e) => self.generate_randn(e),
3820            Expression::Randstr(e) => self.generate_randstr(e),
3821            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3822            Expression::RangeN(e) => self.generate_range_n(e),
3823            Expression::ReadCSV(e) => self.generate_read_csv(e),
3824            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3825            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3826            Expression::Reduce(e) => self.generate_reduce(e),
3827            Expression::Reference(e) => self.generate_reference(e),
3828            Expression::Refresh(e) => self.generate_refresh(e),
3829            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3830            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3831            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3832            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3833            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3834            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3835            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3836            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3837            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3838            Expression::RegrCount(e) => self.generate_regr_count(e),
3839            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3840            Expression::RegrR2(e) => self.generate_regr_r2(e),
3841            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3842            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3843            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3844            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3845            Expression::RegrValx(e) => self.generate_regr_valx(e),
3846            Expression::RegrValy(e) => self.generate_regr_valy(e),
3847            Expression::RemoteWithConnectionModelProperty(e) => {
3848                self.generate_remote_with_connection_model_property(e)
3849            }
3850            Expression::RenameColumn(e) => self.generate_rename_column(e),
3851            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3852            Expression::Returning(e) => self.generate_returning(e),
3853            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3854            Expression::Rollback(e) => self.generate_rollback(e),
3855            Expression::Rollup(e) => self.generate_rollup(e),
3856            Expression::RowFormatDelimitedProperty(e) => {
3857                self.generate_row_format_delimited_property(e)
3858            }
3859            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3860            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3861            Expression::SHA2(e) => self.generate_sha2(e),
3862            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3863            Expression::SafeAdd(e) => self.generate_safe_add(e),
3864            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3865            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3866            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3867            Expression::SampleProperty(e) => self.generate_sample_property(e),
3868            Expression::Schema(e) => self.generate_schema(e),
3869            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3870            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3871            Expression::Search(e) => self.generate_search(e),
3872            Expression::SearchIp(e) => self.generate_search_ip(e),
3873            Expression::SecurityProperty(e) => self.generate_security_property(e),
3874            Expression::SemanticView(e) => self.generate_semantic_view(e),
3875            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3876            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3877            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3878            Expression::Set(e) => self.generate_set(e),
3879            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3880            Expression::SetItem(e) => self.generate_set_item(e),
3881            Expression::SetOperation(e) => self.generate_set_operation(e),
3882            Expression::SetProperty(e) => self.generate_set_property(e),
3883            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3884            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3885            Expression::Slice(e) => self.generate_slice(e),
3886            Expression::SortArray(e) => self.generate_sort_array(e),
3887            Expression::SortBy(e) => self.generate_sort_by(e),
3888            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3889            Expression::SplitPart(e) => self.generate_split_part(e),
3890            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3891            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3892            Expression::StDistance(e) => self.generate_st_distance(e),
3893            Expression::StPoint(e) => self.generate_st_point(e),
3894            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3895            Expression::StandardHash(e) => self.generate_standard_hash(e),
3896            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3897            Expression::StrPosition(e) => self.generate_str_position(e),
3898            Expression::StrToDate(e) => self.generate_str_to_date(e),
3899            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3900            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3901            Expression::StrToMap(e) => self.generate_str_to_map(e),
3902            Expression::StrToTime(e) => self.generate_str_to_time(e),
3903            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3904            Expression::StringToArray(e) => self.generate_string_to_array(e),
3905            Expression::Struct(e) => self.generate_struct(e),
3906            Expression::Stuff(e) => self.generate_stuff(e),
3907            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3908            Expression::Summarize(e) => self.generate_summarize(e),
3909            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3910            Expression::TableAlias(e) => self.generate_table_alias(e),
3911            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3912            Expression::RowsFrom(e) => self.generate_rows_from(e),
3913            Expression::TableSample(e) => self.generate_table_sample(e),
3914            Expression::Tag(e) => self.generate_tag(e),
3915            Expression::Tags(e) => self.generate_tags(e),
3916            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3917            Expression::Time(e) => self.generate_time_func(e),
3918            Expression::TimeAdd(e) => self.generate_time_add(e),
3919            Expression::TimeDiff(e) => self.generate_time_diff(e),
3920            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3921            Expression::TimeSlice(e) => self.generate_time_slice(e),
3922            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3923            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3924            Expression::TimeSub(e) => self.generate_time_sub(e),
3925            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3926            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3927            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3928            Expression::TimeUnit(e) => self.generate_time_unit(e),
3929            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3930            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3931            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3932            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3933            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3934            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3935            Expression::ToBinary(e) => self.generate_to_binary(e),
3936            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3937            Expression::ToChar(e) => self.generate_to_char(e),
3938            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3939            Expression::ToDouble(e) => self.generate_to_double(e),
3940            Expression::ToFile(e) => self.generate_to_file(e),
3941            Expression::ToNumber(e) => self.generate_to_number(e),
3942            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3943            Expression::Transaction(e) => self.generate_transaction(e),
3944            Expression::Transform(e) => self.generate_transform(e),
3945            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3946            Expression::TransientProperty(e) => self.generate_transient_property(e),
3947            Expression::Translate(e) => self.generate_translate(e),
3948            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3949            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3950            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3951            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3952            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3953            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3954            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3955            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3956            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3957            Expression::Unhex(e) => self.generate_unhex(e),
3958            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3959            Expression::Uniform(e) => self.generate_uniform(e),
3960            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3961            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3962            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3963            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3964            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3965            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3966            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3967            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3968            Expression::UtcTime(e) => self.generate_utc_time(e),
3969            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3970            Expression::Uuid(e) => self.generate_uuid(e),
3971            Expression::Var(v) => {
3972                if matches!(self.config.dialect, Some(DialectType::MySQL))
3973                    && v.this.len() > 2
3974                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3975                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3976                {
3977                    return self.generate_identifier(&Identifier {
3978                        name: v.this.clone(),
3979                        quoted: true,
3980                        trailing_comments: Vec::new(),
3981                        span: None,
3982                    });
3983                }
3984                self.write(&v.this);
3985                Ok(())
3986            }
3987            Expression::Variadic(e) => {
3988                self.write_keyword("VARIADIC");
3989                self.write_space();
3990                self.generate_expression(&e.this)?;
3991                Ok(())
3992            }
3993            Expression::VarMap(e) => self.generate_var_map(e),
3994            Expression::VectorSearch(e) => self.generate_vector_search(e),
3995            Expression::Version(e) => self.generate_version(e),
3996            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3997            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3998            Expression::WatermarkColumnConstraint(e) => {
3999                self.generate_watermark_column_constraint(e)
4000            }
4001            Expression::Week(e) => self.generate_week(e),
4002            Expression::When(e) => self.generate_when(e),
4003            Expression::Whens(e) => self.generate_whens(e),
4004            Expression::Where(e) => self.generate_where(e),
4005            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4006            Expression::Window(e) => self.generate_window(e),
4007            Expression::WindowSpec(e) => self.generate_window_spec(e),
4008            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4009            Expression::WithFill(e) => self.generate_with_fill(e),
4010            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4011            Expression::WithOperator(e) => self.generate_with_operator(e),
4012            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4013            Expression::WithSchemaBindingProperty(e) => {
4014                self.generate_with_schema_binding_property(e)
4015            }
4016            Expression::WithSystemVersioningProperty(e) => {
4017                self.generate_with_system_versioning_property(e)
4018            }
4019            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4020            Expression::XMLElement(e) => self.generate_xml_element(e),
4021            Expression::XMLGet(e) => self.generate_xml_get(e),
4022            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4023            Expression::XMLTable(e) => self.generate_xml_table(e),
4024            Expression::Xor(e) => self.generate_xor(e),
4025            Expression::Zipf(e) => self.generate_zipf(e),
4026            _ => self.write_unsupported_comment("unsupported expression"),
4027        }
4028    }
4029
4030    fn generate_select(&mut self, select: &Select) -> Result<()> {
4031        use crate::dialects::DialectType;
4032
4033        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4034        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4035        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4036        if let Some(exclude) = &select.exclude {
4037            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4038                // Build the inner select (same as original but without exclude)
4039                let mut inner_select = select.clone();
4040                inner_select.exclude = None;
4041                let inner_expr = Expression::Select(Box::new(inner_select));
4042
4043                // Build the subquery
4044                let subquery = crate::expressions::Subquery {
4045                    this: inner_expr,
4046                    alias: None,
4047                    column_aliases: Vec::new(),
4048                    alias_explicit_as: false,
4049                    alias_keyword: None,
4050                    order_by: None,
4051                    limit: None,
4052                    offset: None,
4053                    distribute_by: None,
4054                    sort_by: None,
4055                    cluster_by: None,
4056                    lateral: false,
4057                    modifiers_inside: false,
4058                    trailing_comments: Vec::new(),
4059                    inferred_type: None,
4060                };
4061
4062                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4063                let star = Expression::Star(crate::expressions::Star {
4064                    table: None,
4065                    except: Some(
4066                        exclude
4067                            .iter()
4068                            .map(|e| match e {
4069                                Expression::Column(col) => col.name.clone(),
4070                                Expression::Identifier(id) => id.clone(),
4071                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4072                            })
4073                            .collect(),
4074                    ),
4075                    replace: None,
4076                    rename: None,
4077                    trailing_comments: Vec::new(),
4078                    span: None,
4079                });
4080
4081                let outer_select = Select {
4082                    expressions: vec![star],
4083                    from: Some(crate::expressions::From {
4084                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4085                    }),
4086                    ..Select::new()
4087                };
4088
4089                return self.generate_select(&outer_select);
4090            }
4091        }
4092
4093        // Output leading comments before SELECT
4094        for comment in &select.leading_comments {
4095            self.write_formatted_comment(comment);
4096            self.write(" ");
4097        }
4098
4099        // WITH clause
4100        if let Some(with) = &select.with {
4101            self.generate_with(with)?;
4102            if self.config.pretty {
4103                self.write_newline();
4104                self.write_indent();
4105            } else {
4106                self.write_space();
4107            }
4108        }
4109
4110        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4111        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4112        for comment in &select.post_select_comments {
4113            self.write_formatted_comment(comment);
4114            self.write(" ");
4115        }
4116
4117        self.write_keyword("SELECT");
4118
4119        // Generate query hint if present /*+ ... */
4120        if let Some(hint) = &select.hint {
4121            self.generate_hint(hint)?;
4122        }
4123
4124        // For SQL Server, convert LIMIT to TOP (structural transformation)
4125        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4126        // TOP clause (SQL Server style - before DISTINCT)
4127        let use_top_from_limit = matches!(
4128            self.config.dialect,
4129            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4130        ) && select.top.is_none()
4131            && select.limit.is_some()
4132            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4133
4134        // For TOP-supporting dialects: DISTINCT before TOP
4135        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4136        let is_top_dialect = matches!(
4137            self.config.dialect,
4138            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4139        );
4140        let keep_top_verbatim = !is_top_dialect
4141            && select.limit.is_none()
4142            && select
4143                .top
4144                .as_ref()
4145                .map_or(false, |top| top.percent || top.with_ties);
4146
4147        if select.distinct && (is_top_dialect || select.top.is_some()) {
4148            self.write_space();
4149            self.write_keyword("DISTINCT");
4150        }
4151
4152        if is_top_dialect || keep_top_verbatim {
4153            if let Some(top) = &select.top {
4154                self.write_space();
4155                self.write_keyword("TOP");
4156                if top.parenthesized {
4157                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4158                        self.write_space();
4159                        self.generate_expression(&top.this)?;
4160                    } else {
4161                        self.write(" (");
4162                        self.generate_expression(&top.this)?;
4163                        self.write(")");
4164                    }
4165                } else {
4166                    self.write_space();
4167                    self.generate_expression(&top.this)?;
4168                }
4169                if top.percent {
4170                    self.write_space();
4171                    self.write_keyword("PERCENT");
4172                }
4173                if top.with_ties {
4174                    self.write_space();
4175                    self.write_keyword("WITH TIES");
4176                }
4177            } else if use_top_from_limit {
4178                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4179                if let Some(limit) = &select.limit {
4180                    self.write_space();
4181                    self.write_keyword("TOP");
4182                    // Use parentheses for complex expressions, but not for simple literals
4183                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4184                    if is_simple_literal {
4185                        self.write_space();
4186                        self.generate_expression(&limit.this)?;
4187                    } else {
4188                        self.write(" (");
4189                        self.generate_expression(&limit.this)?;
4190                        self.write(")");
4191                    }
4192                }
4193            }
4194        }
4195
4196        if select.distinct && !is_top_dialect && select.top.is_none() {
4197            self.write_space();
4198            self.write_keyword("DISTINCT");
4199        }
4200
4201        // DISTINCT ON clause (PostgreSQL)
4202        if let Some(distinct_on) = &select.distinct_on {
4203            self.write_space();
4204            self.write_keyword("ON");
4205            self.write(" (");
4206            for (i, expr) in distinct_on.iter().enumerate() {
4207                if i > 0 {
4208                    self.write(", ");
4209                }
4210                self.generate_expression(expr)?;
4211            }
4212            self.write(")");
4213        }
4214
4215        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4216        for modifier in &select.operation_modifiers {
4217            self.write_space();
4218            self.write_keyword(modifier);
4219        }
4220
4221        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4222        if let Some(kind) = &select.kind {
4223            self.write_space();
4224            self.write_keyword("AS");
4225            self.write_space();
4226            self.write_keyword(kind);
4227        }
4228
4229        // Expressions (only if there are any)
4230        if !select.expressions.is_empty() {
4231            if self.config.pretty {
4232                self.write_newline();
4233                self.indent_level += 1;
4234            } else {
4235                self.write_space();
4236            }
4237        }
4238
4239        for (i, expr) in select.expressions.iter().enumerate() {
4240            if i > 0 {
4241                self.write(",");
4242                if self.config.pretty {
4243                    self.write_newline();
4244                } else {
4245                    self.write_space();
4246                }
4247            }
4248            if self.config.pretty {
4249                self.write_indent();
4250            }
4251            self.generate_expression(expr)?;
4252        }
4253
4254        if self.config.pretty && !select.expressions.is_empty() {
4255            self.indent_level -= 1;
4256        }
4257
4258        // Redshift-style EXCLUDE clause at the end of the projection list
4259        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4260        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4261        // (done after the full select is generated below)
4262        if let Some(exclude) = &select.exclude {
4263            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4264                self.write_space();
4265                self.write_keyword("EXCLUDE");
4266                self.write(" (");
4267                for (i, col) in exclude.iter().enumerate() {
4268                    if i > 0 {
4269                        self.write(", ");
4270                    }
4271                    self.generate_expression(col)?;
4272                }
4273                self.write(")");
4274            }
4275        }
4276
4277        // INTO clause (SELECT ... INTO table_name)
4278        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4279        if let Some(into) = &select.into {
4280            if self.config.pretty {
4281                self.write_newline();
4282                self.write_indent();
4283            } else {
4284                self.write_space();
4285            }
4286            if into.bulk_collect {
4287                self.write_keyword("BULK COLLECT INTO");
4288            } else {
4289                self.write_keyword("INTO");
4290            }
4291            if into.temporary {
4292                self.write_space();
4293                self.write_keyword("TEMPORARY");
4294            }
4295            if into.unlogged {
4296                self.write_space();
4297                self.write_keyword("UNLOGGED");
4298            }
4299            self.write_space();
4300            // If we have multiple expressions, output them comma-separated
4301            if !into.expressions.is_empty() {
4302                for (i, expr) in into.expressions.iter().enumerate() {
4303                    if i > 0 {
4304                        self.write(", ");
4305                    }
4306                    self.generate_expression(expr)?;
4307                }
4308            } else {
4309                self.generate_expression(&into.this)?;
4310            }
4311        }
4312
4313        // FROM clause
4314        if let Some(from) = &select.from {
4315            if self.config.pretty {
4316                self.write_newline();
4317                self.write_indent();
4318            } else {
4319                self.write_space();
4320            }
4321            self.write_keyword("FROM");
4322            self.write_space();
4323
4324            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4325            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4326            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4327            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4328            let has_tablesample = from
4329                .expressions
4330                .iter()
4331                .any(|e| matches!(e, Expression::TableSample(_)));
4332            let is_cross_join_dialect = matches!(
4333                self.config.dialect,
4334                Some(DialectType::BigQuery)
4335                    | Some(DialectType::Hive)
4336                    | Some(DialectType::Spark)
4337                    | Some(DialectType::Databricks)
4338                    | Some(DialectType::SQLite)
4339                    | Some(DialectType::ClickHouse)
4340            );
4341            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4342            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4343            let source_is_same_as_target = self.config.source_dialect.is_some()
4344                && self.config.source_dialect == self.config.dialect;
4345            let source_is_cross_join_dialect = matches!(
4346                self.config.source_dialect,
4347                Some(DialectType::BigQuery)
4348                    | Some(DialectType::Hive)
4349                    | Some(DialectType::Spark)
4350                    | Some(DialectType::Databricks)
4351                    | Some(DialectType::SQLite)
4352                    | Some(DialectType::ClickHouse)
4353            );
4354            let use_cross_join = !has_tablesample
4355                && is_cross_join_dialect
4356                && (source_is_same_as_target
4357                    || source_is_cross_join_dialect
4358                    || self.config.source_dialect.is_none());
4359
4360            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4361            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4362
4363            for (i, expr) in from.expressions.iter().enumerate() {
4364                if i > 0 {
4365                    if use_cross_join {
4366                        self.write(" CROSS JOIN ");
4367                    } else {
4368                        self.write(", ");
4369                    }
4370                }
4371                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4372                    self.write("(");
4373                    self.generate_expression(expr)?;
4374                    self.write(")");
4375                } else {
4376                    self.generate_expression(expr)?;
4377                }
4378                // Output leading comments that were on the table name before FROM
4379                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4380                let leading = Self::extract_table_leading_comments(expr);
4381                for comment in &leading {
4382                    self.write_space();
4383                    self.write_formatted_comment(comment);
4384                }
4385            }
4386        }
4387
4388        // JOINs - handle nested join structure for pretty printing
4389        // Deferred-condition joins "own" the non-deferred joins that follow them
4390        // until the next deferred join or end of list
4391        if self.config.pretty {
4392            self.generate_joins_with_nesting(&select.joins)?;
4393        } else {
4394            for join in &select.joins {
4395                self.generate_join(join)?;
4396            }
4397            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4398            for join in select.joins.iter().rev() {
4399                if join.deferred_condition {
4400                    self.generate_join_condition(join)?;
4401                }
4402            }
4403        }
4404
4405        // LATERAL VIEW clauses (Hive/Spark)
4406        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4407            self.generate_lateral_view(lateral_view, lv_idx)?;
4408        }
4409
4410        // PREWHERE (ClickHouse)
4411        if let Some(prewhere) = &select.prewhere {
4412            self.write_clause_condition("PREWHERE", prewhere)?;
4413        }
4414
4415        // WHERE
4416        if let Some(where_clause) = &select.where_clause {
4417            self.write_clause_condition("WHERE", &where_clause.this)?;
4418        }
4419
4420        // CONNECT BY (Oracle hierarchical queries)
4421        if let Some(connect) = &select.connect {
4422            self.generate_connect(connect)?;
4423        }
4424
4425        // GROUP BY
4426        if let Some(group_by) = &select.group_by {
4427            if self.config.pretty {
4428                // Output leading comments on their own lines before GROUP BY
4429                for comment in &group_by.comments {
4430                    self.write_newline();
4431                    self.write_indent();
4432                    self.write_formatted_comment(comment);
4433                }
4434                self.write_newline();
4435                self.write_indent();
4436            } else {
4437                self.write_space();
4438                // In non-pretty mode, output comments inline
4439                for comment in &group_by.comments {
4440                    self.write_formatted_comment(comment);
4441                    self.write_space();
4442                }
4443            }
4444            let clickhouse_bare_modifiers =
4445                matches!(self.config.dialect, Some(DialectType::ClickHouse))
4446                    && group_by.all.is_none()
4447                    && (group_by.totals || !group_by.expressions.is_empty())
4448                    && group_by.expressions.iter().all(|expr| match expr {
4449                        Expression::Cube(c) => c.expressions.is_empty(),
4450                        Expression::Rollup(r) => r.expressions.is_empty(),
4451                        _ => false,
4452                    });
4453
4454            if clickhouse_bare_modifiers {
4455                let trailing_cube = group_by
4456                    .expressions
4457                    .iter()
4458                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
4459                let trailing_rollup = group_by
4460                    .expressions
4461                    .iter()
4462                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
4463
4464                if trailing_cube {
4465                    self.write_keyword("WITH CUBE");
4466                } else if trailing_rollup {
4467                    self.write_keyword("WITH ROLLUP");
4468                }
4469
4470                if group_by.totals {
4471                    if trailing_cube || trailing_rollup {
4472                        self.write_space();
4473                    }
4474                    self.write_keyword("WITH TOTALS");
4475                }
4476            } else {
4477                self.write_keyword("GROUP BY");
4478                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4479                match group_by.all {
4480                    Some(true) => {
4481                        self.write_space();
4482                        self.write_keyword("ALL");
4483                    }
4484                    Some(false) => {
4485                        self.write_space();
4486                        self.write_keyword("DISTINCT");
4487                    }
4488                    None => {}
4489                }
4490                if !group_by.expressions.is_empty() {
4491                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4492                    // These are represented as Cube/Rollup expressions with empty expressions at the end
4493                    let mut trailing_cube = false;
4494                    let mut trailing_rollup = false;
4495                    let mut plain_expressions: Vec<&Expression> = Vec::new();
4496                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4497                    let mut cube_expressions: Vec<&Expression> = Vec::new();
4498                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
4499
4500                    for expr in &group_by.expressions {
4501                        match expr {
4502                            Expression::Cube(c) if c.expressions.is_empty() => {
4503                                trailing_cube = true;
4504                            }
4505                            Expression::Rollup(r) if r.expressions.is_empty() => {
4506                                trailing_rollup = true;
4507                            }
4508                            Expression::Function(f) if f.name == "CUBE" => {
4509                                cube_expressions.push(expr);
4510                            }
4511                            Expression::Function(f) if f.name == "ROLLUP" => {
4512                                rollup_expressions.push(expr);
4513                            }
4514                            Expression::Function(f) if f.name == "GROUPING SETS" => {
4515                                grouping_sets_expressions.push(expr);
4516                            }
4517                            _ => {
4518                                plain_expressions.push(expr);
4519                            }
4520                        }
4521                    }
4522
4523                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4524                    let mut regular_expressions: Vec<&Expression> = Vec::new();
4525                    regular_expressions.extend(plain_expressions);
4526                    regular_expressions.extend(grouping_sets_expressions);
4527                    regular_expressions.extend(cube_expressions);
4528                    regular_expressions.extend(rollup_expressions);
4529
4530                    if self.config.pretty {
4531                        self.write_newline();
4532                        self.indent_level += 1;
4533                        self.write_indent();
4534                    } else {
4535                        self.write_space();
4536                    }
4537
4538                    for (i, expr) in regular_expressions.iter().enumerate() {
4539                        if i > 0 {
4540                            if self.config.pretty {
4541                                self.write(",");
4542                                self.write_newline();
4543                                self.write_indent();
4544                            } else {
4545                                self.write(", ");
4546                            }
4547                        }
4548                        self.generate_expression(expr)?;
4549                    }
4550
4551                    if self.config.pretty {
4552                        self.indent_level -= 1;
4553                    }
4554
4555                    // Output trailing WITH CUBE or WITH ROLLUP
4556                    if trailing_cube {
4557                        self.write_space();
4558                        self.write_keyword("WITH CUBE");
4559                    } else if trailing_rollup {
4560                        self.write_space();
4561                        self.write_keyword("WITH ROLLUP");
4562                    }
4563                }
4564
4565                // ClickHouse: WITH TOTALS
4566                if group_by.totals {
4567                    self.write_space();
4568                    self.write_keyword("WITH TOTALS");
4569                }
4570            }
4571        }
4572
4573        // HAVING
4574        if let Some(having) = &select.having {
4575            if self.config.pretty {
4576                // Output leading comments on their own lines before HAVING
4577                for comment in &having.comments {
4578                    self.write_newline();
4579                    self.write_indent();
4580                    self.write_formatted_comment(comment);
4581                }
4582            } else {
4583                for comment in &having.comments {
4584                    self.write_space();
4585                    self.write_formatted_comment(comment);
4586                }
4587            }
4588            self.write_clause_condition("HAVING", &having.this)?;
4589        }
4590
4591        // QUALIFY and WINDOW clause ordering depends on input SQL
4592        if select.qualify_after_window {
4593            // WINDOW before QUALIFY (DuckDB style)
4594            if let Some(windows) = &select.windows {
4595                self.write_window_clause(windows)?;
4596            }
4597            if let Some(qualify) = &select.qualify {
4598                self.write_clause_condition("QUALIFY", &qualify.this)?;
4599            }
4600        } else {
4601            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4602            if let Some(qualify) = &select.qualify {
4603                self.write_clause_condition("QUALIFY", &qualify.this)?;
4604            }
4605            if let Some(windows) = &select.windows {
4606                self.write_window_clause(windows)?;
4607            }
4608        }
4609
4610        // DISTRIBUTE BY (Hive/Spark)
4611        if let Some(distribute_by) = &select.distribute_by {
4612            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4613        }
4614
4615        // CLUSTER BY (Hive/Spark)
4616        if let Some(cluster_by) = &select.cluster_by {
4617            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4618        }
4619
4620        // SORT BY (Hive/Spark - comes before ORDER BY)
4621        if let Some(sort_by) = &select.sort_by {
4622            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4623        }
4624
4625        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4626        if let Some(order_by) = &select.order_by {
4627            if self.config.pretty {
4628                // Output leading comments on their own lines before ORDER BY
4629                for comment in &order_by.comments {
4630                    self.write_newline();
4631                    self.write_indent();
4632                    self.write_formatted_comment(comment);
4633                }
4634            } else {
4635                for comment in &order_by.comments {
4636                    self.write_space();
4637                    self.write_formatted_comment(comment);
4638                }
4639            }
4640            let keyword = if order_by.siblings {
4641                "ORDER SIBLINGS BY"
4642            } else {
4643                "ORDER BY"
4644            };
4645            self.write_order_clause(keyword, &order_by.expressions)?;
4646        }
4647
4648        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4649        if select.order_by.is_none()
4650            && select.fetch.is_some()
4651            && matches!(
4652                self.config.dialect,
4653                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4654            )
4655        {
4656            if self.config.pretty {
4657                self.write_newline();
4658                self.write_indent();
4659            } else {
4660                self.write_space();
4661            }
4662            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4663        }
4664
4665        // LIMIT and OFFSET
4666        // PostgreSQL and others use: LIMIT count OFFSET offset
4667        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4668        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4669        let is_presto_like = matches!(
4670            self.config.dialect,
4671            Some(DialectType::Presto) | Some(DialectType::Trino)
4672        );
4673
4674        if is_presto_like && select.offset.is_some() {
4675            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4676            if let Some(offset) = &select.offset {
4677                if self.config.pretty {
4678                    self.write_newline();
4679                    self.write_indent();
4680                } else {
4681                    self.write_space();
4682                }
4683                self.write_keyword("OFFSET");
4684                self.write_space();
4685                self.write_limit_expr(&offset.this)?;
4686                if offset.rows == Some(true) {
4687                    self.write_space();
4688                    self.write_keyword("ROWS");
4689                }
4690            }
4691            if let Some(limit) = &select.limit {
4692                if self.config.pretty {
4693                    self.write_newline();
4694                    self.write_indent();
4695                } else {
4696                    self.write_space();
4697                }
4698                self.write_keyword("LIMIT");
4699                self.write_space();
4700                self.write_limit_expr(&limit.this)?;
4701                if limit.percent {
4702                    self.write_space();
4703                    self.write_keyword("PERCENT");
4704                }
4705                // Emit any comments that were captured from before the LIMIT keyword
4706                for comment in &limit.comments {
4707                    self.write(" ");
4708                    self.write_formatted_comment(comment);
4709                }
4710            }
4711        } else {
4712            // Check if FETCH will be converted to LIMIT (used for ordering)
4713            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4714                !fetch.percent
4715                    && !fetch.with_ties
4716                    && fetch.count.is_some()
4717                    && matches!(
4718                        self.config.dialect,
4719                        Some(DialectType::Spark)
4720                            | Some(DialectType::Hive)
4721                            | Some(DialectType::DuckDB)
4722                            | Some(DialectType::SQLite)
4723                            | Some(DialectType::MySQL)
4724                            | Some(DialectType::BigQuery)
4725                            | Some(DialectType::Databricks)
4726                            | Some(DialectType::StarRocks)
4727                            | Some(DialectType::Doris)
4728                            | Some(DialectType::Athena)
4729                            | Some(DialectType::ClickHouse)
4730                            | Some(DialectType::Redshift)
4731                    )
4732            });
4733
4734            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4735            if let Some(limit) = &select.limit {
4736                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4737                if !matches!(
4738                    self.config.dialect,
4739                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4740                ) {
4741                    if self.config.pretty {
4742                        self.write_newline();
4743                        self.write_indent();
4744                    } else {
4745                        self.write_space();
4746                    }
4747                    self.write_keyword("LIMIT");
4748                    self.write_space();
4749                    self.write_limit_expr(&limit.this)?;
4750                    if limit.percent {
4751                        self.write_space();
4752                        self.write_keyword("PERCENT");
4753                    }
4754                    // Emit any comments that were captured from before the LIMIT keyword
4755                    for comment in &limit.comments {
4756                        self.write(" ");
4757                        self.write_formatted_comment(comment);
4758                    }
4759                }
4760            }
4761
4762            // Convert TOP to LIMIT for non-TOP dialects
4763            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4764                if let Some(top) = &select.top {
4765                    if !top.percent && !top.with_ties {
4766                        if self.config.pretty {
4767                            self.write_newline();
4768                            self.write_indent();
4769                        } else {
4770                            self.write_space();
4771                        }
4772                        self.write_keyword("LIMIT");
4773                        self.write_space();
4774                        self.generate_expression(&top.this)?;
4775                    }
4776                }
4777            }
4778
4779            // If FETCH will be converted to LIMIT and there's also OFFSET,
4780            // emit LIMIT from FETCH BEFORE the OFFSET
4781            if fetch_as_limit && select.offset.is_some() {
4782                if let Some(fetch) = &select.fetch {
4783                    if self.config.pretty {
4784                        self.write_newline();
4785                        self.write_indent();
4786                    } else {
4787                        self.write_space();
4788                    }
4789                    self.write_keyword("LIMIT");
4790                    self.write_space();
4791                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4792                }
4793            }
4794
4795            // OFFSET
4796            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4797            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4798            if let Some(offset) = &select.offset {
4799                if self.config.pretty {
4800                    self.write_newline();
4801                    self.write_indent();
4802                } else {
4803                    self.write_space();
4804                }
4805                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4806                    // SQL Server 2012+ OFFSET ... FETCH syntax
4807                    self.write_keyword("OFFSET");
4808                    self.write_space();
4809                    self.write_limit_expr(&offset.this)?;
4810                    self.write_space();
4811                    self.write_keyword("ROWS");
4812                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4813                    if let Some(limit) = &select.limit {
4814                        self.write_space();
4815                        self.write_keyword("FETCH NEXT");
4816                        self.write_space();
4817                        self.write_limit_expr(&limit.this)?;
4818                        self.write_space();
4819                        self.write_keyword("ROWS ONLY");
4820                    }
4821                } else {
4822                    self.write_keyword("OFFSET");
4823                    self.write_space();
4824                    self.write_limit_expr(&offset.this)?;
4825                    // Output ROWS keyword if it was in the original SQL
4826                    if offset.rows == Some(true) {
4827                        self.write_space();
4828                        self.write_keyword("ROWS");
4829                    }
4830                }
4831            }
4832        }
4833
4834        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4835        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4836            if let Some(limit_by) = &select.limit_by {
4837                if !limit_by.is_empty() {
4838                    self.write_space();
4839                    self.write_keyword("BY");
4840                    self.write_space();
4841                    for (i, expr) in limit_by.iter().enumerate() {
4842                        if i > 0 {
4843                            self.write(", ");
4844                        }
4845                        self.generate_expression(expr)?;
4846                    }
4847                }
4848            }
4849        }
4850
4851        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4852        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4853            if let Some(settings) = &select.settings {
4854                if self.config.pretty {
4855                    self.write_newline();
4856                    self.write_indent();
4857                } else {
4858                    self.write_space();
4859                }
4860                self.write_keyword("SETTINGS");
4861                self.write_space();
4862                for (i, expr) in settings.iter().enumerate() {
4863                    if i > 0 {
4864                        self.write(", ");
4865                    }
4866                    self.generate_expression(expr)?;
4867                }
4868            }
4869
4870            if let Some(format_expr) = &select.format {
4871                if self.config.pretty {
4872                    self.write_newline();
4873                    self.write_indent();
4874                } else {
4875                    self.write_space();
4876                }
4877                self.write_keyword("FORMAT");
4878                self.write_space();
4879                self.generate_expression(format_expr)?;
4880            }
4881        }
4882
4883        // FETCH FIRST/NEXT
4884        if let Some(fetch) = &select.fetch {
4885            // Check if we already emitted LIMIT from FETCH before OFFSET
4886            let fetch_already_as_limit = select.offset.is_some()
4887                && !fetch.percent
4888                && !fetch.with_ties
4889                && fetch.count.is_some()
4890                && matches!(
4891                    self.config.dialect,
4892                    Some(DialectType::Spark)
4893                        | Some(DialectType::Hive)
4894                        | Some(DialectType::DuckDB)
4895                        | Some(DialectType::SQLite)
4896                        | Some(DialectType::MySQL)
4897                        | Some(DialectType::BigQuery)
4898                        | Some(DialectType::Databricks)
4899                        | Some(DialectType::StarRocks)
4900                        | Some(DialectType::Doris)
4901                        | Some(DialectType::Athena)
4902                        | Some(DialectType::ClickHouse)
4903                        | Some(DialectType::Redshift)
4904                );
4905
4906            if fetch_already_as_limit {
4907                // Already emitted as LIMIT before OFFSET, skip
4908            } else {
4909                if self.config.pretty {
4910                    self.write_newline();
4911                    self.write_indent();
4912                } else {
4913                    self.write_space();
4914                }
4915
4916                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4917                let use_limit = !fetch.percent
4918                    && !fetch.with_ties
4919                    && fetch.count.is_some()
4920                    && matches!(
4921                        self.config.dialect,
4922                        Some(DialectType::Spark)
4923                            | Some(DialectType::Hive)
4924                            | Some(DialectType::DuckDB)
4925                            | Some(DialectType::SQLite)
4926                            | Some(DialectType::MySQL)
4927                            | Some(DialectType::BigQuery)
4928                            | Some(DialectType::Databricks)
4929                            | Some(DialectType::StarRocks)
4930                            | Some(DialectType::Doris)
4931                            | Some(DialectType::Athena)
4932                            | Some(DialectType::ClickHouse)
4933                            | Some(DialectType::Redshift)
4934                    );
4935
4936                if use_limit {
4937                    self.write_keyword("LIMIT");
4938                    self.write_space();
4939                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4940                } else {
4941                    self.write_keyword("FETCH");
4942                    self.write_space();
4943                    self.write_keyword(&fetch.direction);
4944                    if let Some(ref count) = fetch.count {
4945                        self.write_space();
4946                        self.generate_expression(count)?;
4947                    }
4948                    if fetch.percent {
4949                        self.write_space();
4950                        self.write_keyword("PERCENT");
4951                    }
4952                    if fetch.rows {
4953                        self.write_space();
4954                        self.write_keyword("ROWS");
4955                    }
4956                    if fetch.with_ties {
4957                        self.write_space();
4958                        self.write_keyword("WITH TIES");
4959                    } else {
4960                        self.write_space();
4961                        self.write_keyword("ONLY");
4962                    }
4963                }
4964            } // close fetch_already_as_limit else
4965        }
4966
4967        // SAMPLE / TABLESAMPLE
4968        if let Some(sample) = &select.sample {
4969            use crate::dialects::DialectType;
4970            if self.config.pretty {
4971                self.write_newline();
4972            } else {
4973                self.write_space();
4974            }
4975
4976            if sample.is_using_sample {
4977                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4978                self.write_keyword("USING SAMPLE");
4979                self.generate_sample_body(sample)?;
4980            } else {
4981                self.write_keyword("TABLESAMPLE");
4982
4983                // Snowflake defaults to BERNOULLI when no explicit method is given
4984                let snowflake_bernoulli =
4985                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4986                        && !sample.explicit_method;
4987                if snowflake_bernoulli {
4988                    self.write_space();
4989                    self.write_keyword("BERNOULLI");
4990                }
4991
4992                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4993                if matches!(sample.method, SampleMethod::Bucket) {
4994                    self.write_space();
4995                    self.write("(");
4996                    self.write_keyword("BUCKET");
4997                    self.write_space();
4998                    if let Some(ref num) = sample.bucket_numerator {
4999                        self.generate_expression(num)?;
5000                    }
5001                    self.write_space();
5002                    self.write_keyword("OUT OF");
5003                    self.write_space();
5004                    if let Some(ref denom) = sample.bucket_denominator {
5005                        self.generate_expression(denom)?;
5006                    }
5007                    if let Some(ref field) = sample.bucket_field {
5008                        self.write_space();
5009                        self.write_keyword("ON");
5010                        self.write_space();
5011                        self.generate_expression(field)?;
5012                    }
5013                    self.write(")");
5014                } else if sample.unit_after_size {
5015                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5016                    if sample.explicit_method && sample.method_before_size {
5017                        self.write_space();
5018                        match sample.method {
5019                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5020                            SampleMethod::System => self.write_keyword("SYSTEM"),
5021                            SampleMethod::Block => self.write_keyword("BLOCK"),
5022                            SampleMethod::Row => self.write_keyword("ROW"),
5023                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5024                            _ => {}
5025                        }
5026                    }
5027                    self.write(" (");
5028                    self.generate_expression(&sample.size)?;
5029                    self.write_space();
5030                    match sample.method {
5031                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5032                        SampleMethod::Row => self.write_keyword("ROWS"),
5033                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5034                        _ => {
5035                            self.write_keyword("PERCENT");
5036                        }
5037                    }
5038                    self.write(")");
5039                } else {
5040                    // Syntax: TABLESAMPLE METHOD (size)
5041                    self.write_space();
5042                    match sample.method {
5043                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5044                        SampleMethod::System => self.write_keyword("SYSTEM"),
5045                        SampleMethod::Block => self.write_keyword("BLOCK"),
5046                        SampleMethod::Row => self.write_keyword("ROW"),
5047                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5048                        SampleMethod::Bucket => {}
5049                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5050                    }
5051                    self.write(" (");
5052                    self.generate_expression(&sample.size)?;
5053                    if matches!(sample.method, SampleMethod::Percent) {
5054                        self.write_space();
5055                        self.write_keyword("PERCENT");
5056                    }
5057                    self.write(")");
5058                }
5059            }
5060
5061            if let Some(seed) = &sample.seed {
5062                self.write_space();
5063                // Databricks/Spark use REPEATABLE, not SEED
5064                let use_seed = sample.use_seed_keyword
5065                    && !matches!(
5066                        self.config.dialect,
5067                        Some(crate::dialects::DialectType::Databricks)
5068                            | Some(crate::dialects::DialectType::Spark)
5069                    );
5070                if use_seed {
5071                    self.write_keyword("SEED");
5072                } else {
5073                    self.write_keyword("REPEATABLE");
5074                }
5075                self.write(" (");
5076                self.generate_expression(seed)?;
5077                self.write(")");
5078            }
5079        }
5080
5081        // FOR UPDATE/SHARE locks
5082        // Skip locking clauses for dialects that don't support them
5083        if self.config.locking_reads_supported {
5084            for lock in &select.locks {
5085                if self.config.pretty {
5086                    self.write_newline();
5087                    self.write_indent();
5088                } else {
5089                    self.write_space();
5090                }
5091                self.generate_lock(lock)?;
5092            }
5093        }
5094
5095        // FOR XML clause (T-SQL)
5096        if !select.for_xml.is_empty() {
5097            if self.config.pretty {
5098                self.write_newline();
5099                self.write_indent();
5100            } else {
5101                self.write_space();
5102            }
5103            self.write_keyword("FOR XML");
5104            for (i, opt) in select.for_xml.iter().enumerate() {
5105                if self.config.pretty {
5106                    if i > 0 {
5107                        self.write(",");
5108                    }
5109                    self.write_newline();
5110                    self.write_indent();
5111                    self.write("  "); // extra indent for options
5112                } else {
5113                    if i > 0 {
5114                        self.write(",");
5115                    }
5116                    self.write_space();
5117                }
5118                self.generate_for_xml_option(opt)?;
5119            }
5120        }
5121
5122        // FOR JSON clause (T-SQL)
5123        if !select.for_json.is_empty() {
5124            if self.config.pretty {
5125                self.write_newline();
5126                self.write_indent();
5127            } else {
5128                self.write_space();
5129            }
5130            self.write_keyword("FOR JSON");
5131            for (i, opt) in select.for_json.iter().enumerate() {
5132                if self.config.pretty {
5133                    if i > 0 {
5134                        self.write(",");
5135                    }
5136                    self.write_newline();
5137                    self.write_indent();
5138                    self.write("  "); // extra indent for options
5139                } else {
5140                    if i > 0 {
5141                        self.write(",");
5142                    }
5143                    self.write_space();
5144                }
5145                self.generate_for_xml_option(opt)?;
5146            }
5147        }
5148
5149        // TSQL: OPTION clause
5150        if let Some(ref option) = select.option {
5151            if matches!(
5152                self.config.dialect,
5153                Some(crate::dialects::DialectType::TSQL)
5154                    | Some(crate::dialects::DialectType::Fabric)
5155            ) {
5156                self.write_space();
5157                self.write(option);
5158            }
5159        }
5160
5161        Ok(())
5162    }
5163
5164    /// Generate a single FOR XML option
5165    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5166        match opt {
5167            Expression::QueryOption(qo) => {
5168                // Extract the option name from Var
5169                if let Expression::Var(var) = &*qo.this {
5170                    self.write(&var.this);
5171                } else {
5172                    self.generate_expression(&qo.this)?;
5173                }
5174                // If there's an expression (like PATH('element')), output it in parens
5175                if let Some(expr) = &qo.expression {
5176                    self.write("(");
5177                    self.generate_expression(expr)?;
5178                    self.write(")");
5179                }
5180            }
5181            _ => {
5182                self.generate_expression(opt)?;
5183            }
5184        }
5185        Ok(())
5186    }
5187
5188    fn generate_with(&mut self, with: &With) -> Result<()> {
5189        use crate::dialects::DialectType;
5190
5191        // Output leading comments before WITH
5192        for comment in &with.leading_comments {
5193            self.write_formatted_comment(comment);
5194            self.write(" ");
5195        }
5196        self.write_keyword("WITH");
5197        if with.recursive && self.config.cte_recursive_keyword_required {
5198            self.write_space();
5199            self.write_keyword("RECURSIVE");
5200        }
5201        self.write_space();
5202
5203        // BigQuery doesn't support column aliases in CTE definitions
5204        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5205
5206        for (i, cte) in with.ctes.iter().enumerate() {
5207            if i > 0 {
5208                self.write(",");
5209                if self.config.pretty {
5210                    self.write_space();
5211                } else {
5212                    self.write(" ");
5213                }
5214            }
5215            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5216                self.generate_expression(&cte.this)?;
5217                self.write_space();
5218                self.write_keyword("AS");
5219                self.write_space();
5220                self.generate_identifier(&cte.alias)?;
5221                continue;
5222            }
5223            self.generate_identifier(&cte.alias)?;
5224            // Output CTE comments after alias name, before AS
5225            for comment in &cte.comments {
5226                self.write_space();
5227                self.write_formatted_comment(comment);
5228            }
5229            if !cte.columns.is_empty() && !skip_cte_columns {
5230                self.write("(");
5231                for (j, col) in cte.columns.iter().enumerate() {
5232                    if j > 0 {
5233                        self.write(", ");
5234                    }
5235                    self.generate_identifier(col)?;
5236                }
5237                self.write(")");
5238            }
5239            // USING KEY (columns) for DuckDB recursive CTEs
5240            if !cte.key_expressions.is_empty() {
5241                self.write_space();
5242                self.write_keyword("USING KEY");
5243                self.write(" (");
5244                for (i, key) in cte.key_expressions.iter().enumerate() {
5245                    if i > 0 {
5246                        self.write(", ");
5247                    }
5248                    self.generate_identifier(key)?;
5249                }
5250                self.write(")");
5251            }
5252            self.write_space();
5253            self.write_keyword("AS");
5254            // MATERIALIZED / NOT MATERIALIZED
5255            if let Some(materialized) = cte.materialized {
5256                self.write_space();
5257                if materialized {
5258                    self.write_keyword("MATERIALIZED");
5259                } else {
5260                    self.write_keyword("NOT MATERIALIZED");
5261                }
5262            }
5263            self.write(" (");
5264            if self.config.pretty {
5265                self.write_newline();
5266                self.indent_level += 1;
5267                self.write_indent();
5268            }
5269            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5270            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5271            let wrap_values_in_select = matches!(
5272                self.config.dialect,
5273                Some(DialectType::Spark) | Some(DialectType::Databricks)
5274            ) && matches!(&cte.this, Expression::Values(_));
5275
5276            if wrap_values_in_select {
5277                self.write_keyword("SELECT");
5278                self.write(" * ");
5279                self.write_keyword("FROM");
5280                self.write_space();
5281            }
5282            self.generate_expression(&cte.this)?;
5283            if self.config.pretty {
5284                self.write_newline();
5285                self.indent_level -= 1;
5286                self.write_indent();
5287            }
5288            self.write(")");
5289        }
5290
5291        // Generate SEARCH/CYCLE clause if present
5292        if let Some(search) = &with.search {
5293            self.write_space();
5294            self.generate_expression(search)?;
5295        }
5296
5297        Ok(())
5298    }
5299
5300    /// Generate joins with proper nesting structure for pretty printing.
5301    /// Deferred-condition joins "own" the non-deferred joins that follow them
5302    /// within the same nesting_group.
5303    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5304        let mut i = 0;
5305        while i < joins.len() {
5306            if joins[i].deferred_condition {
5307                let parent_group = joins[i].nesting_group;
5308
5309                // This join owns the following non-deferred joins in the same nesting_group
5310                // First output the join keyword and table (without condition)
5311                self.generate_join_without_condition(&joins[i])?;
5312
5313                // Find the range of child joins: same nesting_group and not deferred
5314                let child_start = i + 1;
5315                let mut child_end = child_start;
5316                while child_end < joins.len()
5317                    && !joins[child_end].deferred_condition
5318                    && joins[child_end].nesting_group == parent_group
5319                {
5320                    child_end += 1;
5321                }
5322
5323                // Output child joins with extra indentation
5324                if child_start < child_end {
5325                    self.indent_level += 1;
5326                    for j in child_start..child_end {
5327                        self.generate_join(&joins[j])?;
5328                    }
5329                    self.indent_level -= 1;
5330                }
5331
5332                // Output the deferred condition at the parent level
5333                self.generate_join_condition(&joins[i])?;
5334
5335                i = child_end;
5336            } else {
5337                // Regular join (no nesting)
5338                self.generate_join(&joins[i])?;
5339                i += 1;
5340            }
5341        }
5342        Ok(())
5343    }
5344
5345    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5346    /// Used for deferred-condition joins where the condition is output after child joins.
5347    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5348        // Save and temporarily clear the condition to prevent generate_join from outputting it
5349        // We achieve this by creating a modified copy
5350        let mut join_copy = join.clone();
5351        join_copy.on = None;
5352        join_copy.using = Vec::new();
5353        join_copy.deferred_condition = false;
5354        self.generate_join(&join_copy)
5355    }
5356
5357    fn generate_join(&mut self, join: &Join) -> Result<()> {
5358        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5359        if join.kind == JoinKind::Implicit {
5360            self.write(",");
5361            if self.config.pretty {
5362                self.write_newline();
5363                self.write_indent();
5364            } else {
5365                self.write_space();
5366            }
5367            self.generate_expression(&join.this)?;
5368            return Ok(());
5369        }
5370
5371        if self.config.pretty {
5372            self.write_newline();
5373            self.write_indent();
5374        } else {
5375            self.write_space();
5376        }
5377
5378        // Helper: format hint suffix (e.g., " LOOP" or "")
5379        // Only include join hints for dialects that support them
5380        let hint_str = if self.config.join_hints {
5381            join.join_hint
5382                .as_ref()
5383                .map(|h| format!(" {}", h))
5384                .unwrap_or_default()
5385        } else {
5386            String::new()
5387        };
5388
5389        let clickhouse_join_keyword =
5390            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5391                if let Some(hint) = &join.join_hint {
5392                    let mut global = false;
5393                    let mut strictness: Option<&'static str> = None;
5394                    for part in hint.split_whitespace() {
5395                        if part.eq_ignore_ascii_case("GLOBAL") {
5396                            global = true;
5397                        } else if part.eq_ignore_ascii_case("ALL") {
5398                            strictness = Some("ALL");
5399                        } else if part.eq_ignore_ascii_case("ANY") {
5400                            strictness = Some("ANY");
5401                        } else if part.eq_ignore_ascii_case("ASOF") {
5402                            strictness = Some("ASOF");
5403                        } else if part.eq_ignore_ascii_case("SEMI") {
5404                            strictness = Some("SEMI");
5405                        } else if part.eq_ignore_ascii_case("ANTI") {
5406                            strictness = Some("ANTI");
5407                        }
5408                    }
5409
5410                    if global || strictness.is_some() {
5411                        let join_type = match join.kind {
5412                            JoinKind::Left => {
5413                                if join.use_outer_keyword {
5414                                    "LEFT OUTER"
5415                                } else if join.use_inner_keyword {
5416                                    "LEFT INNER"
5417                                } else {
5418                                    "LEFT"
5419                                }
5420                            }
5421                            JoinKind::Right => {
5422                                if join.use_outer_keyword {
5423                                    "RIGHT OUTER"
5424                                } else if join.use_inner_keyword {
5425                                    "RIGHT INNER"
5426                                } else {
5427                                    "RIGHT"
5428                                }
5429                            }
5430                            JoinKind::Full => {
5431                                if join.use_outer_keyword {
5432                                    "FULL OUTER"
5433                                } else {
5434                                    "FULL"
5435                                }
5436                            }
5437                            JoinKind::Inner => {
5438                                if join.use_inner_keyword {
5439                                    "INNER"
5440                                } else {
5441                                    ""
5442                                }
5443                            }
5444                            _ => "",
5445                        };
5446
5447                        let mut parts = Vec::new();
5448                        if global {
5449                            parts.push("GLOBAL");
5450                        }
5451                        if !join_type.is_empty() {
5452                            parts.push(join_type);
5453                        }
5454                        if let Some(strict) = strictness {
5455                            parts.push(strict);
5456                        }
5457                        parts.push("JOIN");
5458                        Some(parts.join(" "))
5459                    } else {
5460                        None
5461                    }
5462                } else {
5463                    None
5464                }
5465            } else {
5466                None
5467            };
5468
5469        // Output any comments associated with this join
5470        // In pretty mode, comments go on their own line before the join keyword
5471        // In non-pretty mode, comments go inline before the join keyword
5472        if !join.comments.is_empty() {
5473            if self.config.pretty {
5474                // In pretty mode, go back before the newline+indent we just wrote
5475                // and output comments on their own lines
5476                // We need to output comments BEFORE the join keyword on separate lines
5477                // Trim the trailing newline+indent we already wrote
5478                let trimmed = self.output.trim_end().len();
5479                self.output.truncate(trimmed);
5480                for comment in &join.comments {
5481                    self.write_newline();
5482                    self.write_indent();
5483                    self.write_formatted_comment(comment);
5484                }
5485                self.write_newline();
5486                self.write_indent();
5487            } else {
5488                for comment in &join.comments {
5489                    self.write_formatted_comment(comment);
5490                    self.write_space();
5491                }
5492            }
5493        }
5494
5495        let directed_str = if join.directed { " DIRECTED" } else { "" };
5496
5497        if let Some(keyword) = clickhouse_join_keyword {
5498            self.write_keyword(&keyword);
5499        } else {
5500            match join.kind {
5501                JoinKind::Inner => {
5502                    if join.use_inner_keyword {
5503                        if hint_str.is_empty() && directed_str.is_empty() {
5504                            self.write_keyword("INNER JOIN");
5505                        } else {
5506                            self.write_keyword("INNER");
5507                            if !hint_str.is_empty() {
5508                                self.write_keyword(&hint_str);
5509                            }
5510                            if !directed_str.is_empty() {
5511                                self.write_keyword(directed_str);
5512                            }
5513                            self.write_keyword(" JOIN");
5514                        }
5515                    } else {
5516                        if !hint_str.is_empty() {
5517                            self.write_keyword(hint_str.trim());
5518                            self.write_keyword(" ");
5519                        }
5520                        if !directed_str.is_empty() {
5521                            self.write_keyword("DIRECTED ");
5522                        }
5523                        self.write_keyword("JOIN");
5524                    }
5525                }
5526                JoinKind::Left => {
5527                    if join.use_outer_keyword {
5528                        if hint_str.is_empty() && directed_str.is_empty() {
5529                            self.write_keyword("LEFT OUTER JOIN");
5530                        } else {
5531                            self.write_keyword("LEFT OUTER");
5532                            if !hint_str.is_empty() {
5533                                self.write_keyword(&hint_str);
5534                            }
5535                            if !directed_str.is_empty() {
5536                                self.write_keyword(directed_str);
5537                            }
5538                            self.write_keyword(" JOIN");
5539                        }
5540                    } else if join.use_inner_keyword {
5541                        if hint_str.is_empty() && directed_str.is_empty() {
5542                            self.write_keyword("LEFT INNER JOIN");
5543                        } else {
5544                            self.write_keyword("LEFT INNER");
5545                            if !hint_str.is_empty() {
5546                                self.write_keyword(&hint_str);
5547                            }
5548                            if !directed_str.is_empty() {
5549                                self.write_keyword(directed_str);
5550                            }
5551                            self.write_keyword(" JOIN");
5552                        }
5553                    } else {
5554                        if hint_str.is_empty() && directed_str.is_empty() {
5555                            self.write_keyword("LEFT JOIN");
5556                        } else {
5557                            self.write_keyword("LEFT");
5558                            if !hint_str.is_empty() {
5559                                self.write_keyword(&hint_str);
5560                            }
5561                            if !directed_str.is_empty() {
5562                                self.write_keyword(directed_str);
5563                            }
5564                            self.write_keyword(" JOIN");
5565                        }
5566                    }
5567                }
5568                JoinKind::Right => {
5569                    if join.use_outer_keyword {
5570                        if hint_str.is_empty() && directed_str.is_empty() {
5571                            self.write_keyword("RIGHT OUTER JOIN");
5572                        } else {
5573                            self.write_keyword("RIGHT OUTER");
5574                            if !hint_str.is_empty() {
5575                                self.write_keyword(&hint_str);
5576                            }
5577                            if !directed_str.is_empty() {
5578                                self.write_keyword(directed_str);
5579                            }
5580                            self.write_keyword(" JOIN");
5581                        }
5582                    } else if join.use_inner_keyword {
5583                        if hint_str.is_empty() && directed_str.is_empty() {
5584                            self.write_keyword("RIGHT INNER JOIN");
5585                        } else {
5586                            self.write_keyword("RIGHT INNER");
5587                            if !hint_str.is_empty() {
5588                                self.write_keyword(&hint_str);
5589                            }
5590                            if !directed_str.is_empty() {
5591                                self.write_keyword(directed_str);
5592                            }
5593                            self.write_keyword(" JOIN");
5594                        }
5595                    } else {
5596                        if hint_str.is_empty() && directed_str.is_empty() {
5597                            self.write_keyword("RIGHT JOIN");
5598                        } else {
5599                            self.write_keyword("RIGHT");
5600                            if !hint_str.is_empty() {
5601                                self.write_keyword(&hint_str);
5602                            }
5603                            if !directed_str.is_empty() {
5604                                self.write_keyword(directed_str);
5605                            }
5606                            self.write_keyword(" JOIN");
5607                        }
5608                    }
5609                }
5610                JoinKind::Full => {
5611                    if join.use_outer_keyword {
5612                        if hint_str.is_empty() && directed_str.is_empty() {
5613                            self.write_keyword("FULL OUTER JOIN");
5614                        } else {
5615                            self.write_keyword("FULL OUTER");
5616                            if !hint_str.is_empty() {
5617                                self.write_keyword(&hint_str);
5618                            }
5619                            if !directed_str.is_empty() {
5620                                self.write_keyword(directed_str);
5621                            }
5622                            self.write_keyword(" JOIN");
5623                        }
5624                    } else {
5625                        if hint_str.is_empty() && directed_str.is_empty() {
5626                            self.write_keyword("FULL JOIN");
5627                        } else {
5628                            self.write_keyword("FULL");
5629                            if !hint_str.is_empty() {
5630                                self.write_keyword(&hint_str);
5631                            }
5632                            if !directed_str.is_empty() {
5633                                self.write_keyword(directed_str);
5634                            }
5635                            self.write_keyword(" JOIN");
5636                        }
5637                    }
5638                }
5639                JoinKind::Outer => {
5640                    if directed_str.is_empty() {
5641                        self.write_keyword("OUTER JOIN");
5642                    } else {
5643                        self.write_keyword("OUTER");
5644                        self.write_keyword(directed_str);
5645                        self.write_keyword(" JOIN");
5646                    }
5647                }
5648                JoinKind::Cross => {
5649                    if directed_str.is_empty() {
5650                        self.write_keyword("CROSS JOIN");
5651                    } else {
5652                        self.write_keyword("CROSS");
5653                        self.write_keyword(directed_str);
5654                        self.write_keyword(" JOIN");
5655                    }
5656                }
5657                JoinKind::Natural => {
5658                    if join.use_inner_keyword {
5659                        if directed_str.is_empty() {
5660                            self.write_keyword("NATURAL INNER JOIN");
5661                        } else {
5662                            self.write_keyword("NATURAL INNER");
5663                            self.write_keyword(directed_str);
5664                            self.write_keyword(" JOIN");
5665                        }
5666                    } else {
5667                        if directed_str.is_empty() {
5668                            self.write_keyword("NATURAL JOIN");
5669                        } else {
5670                            self.write_keyword("NATURAL");
5671                            self.write_keyword(directed_str);
5672                            self.write_keyword(" JOIN");
5673                        }
5674                    }
5675                }
5676                JoinKind::NaturalLeft => {
5677                    if join.use_outer_keyword {
5678                        if directed_str.is_empty() {
5679                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5680                        } else {
5681                            self.write_keyword("NATURAL LEFT OUTER");
5682                            self.write_keyword(directed_str);
5683                            self.write_keyword(" JOIN");
5684                        }
5685                    } else {
5686                        if directed_str.is_empty() {
5687                            self.write_keyword("NATURAL LEFT JOIN");
5688                        } else {
5689                            self.write_keyword("NATURAL LEFT");
5690                            self.write_keyword(directed_str);
5691                            self.write_keyword(" JOIN");
5692                        }
5693                    }
5694                }
5695                JoinKind::NaturalRight => {
5696                    if join.use_outer_keyword {
5697                        if directed_str.is_empty() {
5698                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5699                        } else {
5700                            self.write_keyword("NATURAL RIGHT OUTER");
5701                            self.write_keyword(directed_str);
5702                            self.write_keyword(" JOIN");
5703                        }
5704                    } else {
5705                        if directed_str.is_empty() {
5706                            self.write_keyword("NATURAL RIGHT JOIN");
5707                        } else {
5708                            self.write_keyword("NATURAL RIGHT");
5709                            self.write_keyword(directed_str);
5710                            self.write_keyword(" JOIN");
5711                        }
5712                    }
5713                }
5714                JoinKind::NaturalFull => {
5715                    if join.use_outer_keyword {
5716                        if directed_str.is_empty() {
5717                            self.write_keyword("NATURAL FULL OUTER JOIN");
5718                        } else {
5719                            self.write_keyword("NATURAL FULL OUTER");
5720                            self.write_keyword(directed_str);
5721                            self.write_keyword(" JOIN");
5722                        }
5723                    } else {
5724                        if directed_str.is_empty() {
5725                            self.write_keyword("NATURAL FULL JOIN");
5726                        } else {
5727                            self.write_keyword("NATURAL FULL");
5728                            self.write_keyword(directed_str);
5729                            self.write_keyword(" JOIN");
5730                        }
5731                    }
5732                }
5733                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5734                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5735                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5736                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5737                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5738                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5739                JoinKind::CrossApply => {
5740                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5741                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5742                        self.write_keyword("CROSS APPLY");
5743                    } else {
5744                        self.write_keyword("INNER JOIN LATERAL");
5745                    }
5746                }
5747                JoinKind::OuterApply => {
5748                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5749                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5750                        self.write_keyword("OUTER APPLY");
5751                    } else {
5752                        self.write_keyword("LEFT JOIN LATERAL");
5753                    }
5754                }
5755                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5756                JoinKind::AsOfLeft => {
5757                    if join.use_outer_keyword {
5758                        self.write_keyword("ASOF LEFT OUTER JOIN");
5759                    } else {
5760                        self.write_keyword("ASOF LEFT JOIN");
5761                    }
5762                }
5763                JoinKind::AsOfRight => {
5764                    if join.use_outer_keyword {
5765                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5766                    } else {
5767                        self.write_keyword("ASOF RIGHT JOIN");
5768                    }
5769                }
5770                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5771                JoinKind::LeftLateral => {
5772                    if join.use_outer_keyword {
5773                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5774                    } else {
5775                        self.write_keyword("LEFT LATERAL JOIN");
5776                    }
5777                }
5778                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5779                JoinKind::Implicit => {
5780                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5781                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5782                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5783                    use crate::dialects::DialectType;
5784                    let is_cj_dialect = matches!(
5785                        self.config.dialect,
5786                        Some(DialectType::BigQuery)
5787                            | Some(DialectType::Hive)
5788                            | Some(DialectType::Spark)
5789                            | Some(DialectType::Databricks)
5790                    );
5791                    let source_is_same = self.config.source_dialect.is_some()
5792                        && self.config.source_dialect == self.config.dialect;
5793                    let source_is_cj = matches!(
5794                        self.config.source_dialect,
5795                        Some(DialectType::BigQuery)
5796                            | Some(DialectType::Hive)
5797                            | Some(DialectType::Spark)
5798                            | Some(DialectType::Databricks)
5799                    );
5800                    if is_cj_dialect
5801                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5802                    {
5803                        self.write_keyword("CROSS JOIN");
5804                    } else {
5805                        // Implicit join uses comma: FROM a, b
5806                        // We already wrote a space before the match, so replace with comma
5807                        // by removing trailing space and writing ", "
5808                        self.output.truncate(self.output.trim_end().len());
5809                        self.write(",");
5810                    }
5811                }
5812                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5813                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5814                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5815                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5816            }
5817        }
5818
5819        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5820        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5821            match &join.this {
5822                Expression::Tuple(t) if t.expressions.is_empty() => {}
5823                Expression::Tuple(t) => {
5824                    self.write_space();
5825                    for (i, item) in t.expressions.iter().enumerate() {
5826                        if i > 0 {
5827                            self.write(", ");
5828                        }
5829                        self.generate_expression(item)?;
5830                    }
5831                }
5832                other => {
5833                    self.write_space();
5834                    self.generate_expression(other)?;
5835                }
5836            }
5837        } else {
5838            self.write_space();
5839            self.generate_expression(&join.this)?;
5840        }
5841
5842        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5843        if !join.deferred_condition {
5844            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5845            if let Some(match_cond) = &join.match_condition {
5846                self.write_space();
5847                self.write_keyword("MATCH_CONDITION");
5848                self.write(" (");
5849                self.generate_expression(match_cond)?;
5850                self.write(")");
5851            }
5852
5853            if let Some(on) = &join.on {
5854                if self.config.pretty {
5855                    self.write_newline();
5856                    self.indent_level += 1;
5857                    self.write_indent();
5858                    self.write_keyword("ON");
5859                    self.write_space();
5860                    self.generate_join_on_condition(on)?;
5861                    self.indent_level -= 1;
5862                } else {
5863                    self.write_space();
5864                    self.write_keyword("ON");
5865                    self.write_space();
5866                    self.generate_expression(on)?;
5867                }
5868            }
5869
5870            if !join.using.is_empty() {
5871                if self.config.pretty {
5872                    self.write_newline();
5873                    self.indent_level += 1;
5874                    self.write_indent();
5875                    self.write_keyword("USING");
5876                    self.write(" (");
5877                    for (i, col) in join.using.iter().enumerate() {
5878                        if i > 0 {
5879                            self.write(", ");
5880                        }
5881                        self.generate_identifier(col)?;
5882                    }
5883                    self.write(")");
5884                    self.indent_level -= 1;
5885                } else {
5886                    self.write_space();
5887                    self.write_keyword("USING");
5888                    self.write(" (");
5889                    for (i, col) in join.using.iter().enumerate() {
5890                        if i > 0 {
5891                            self.write(", ");
5892                        }
5893                        self.generate_identifier(col)?;
5894                    }
5895                    self.write(")");
5896                }
5897            }
5898        }
5899
5900        // Generate PIVOT/UNPIVOT expressions that follow this join
5901        for pivot in &join.pivots {
5902            self.write_space();
5903            self.generate_expression(pivot)?;
5904        }
5905
5906        Ok(())
5907    }
5908
5909    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5910    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5911        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5912        if let Some(match_cond) = &join.match_condition {
5913            self.write_space();
5914            self.write_keyword("MATCH_CONDITION");
5915            self.write(" (");
5916            self.generate_expression(match_cond)?;
5917            self.write(")");
5918        }
5919
5920        if let Some(on) = &join.on {
5921            if self.config.pretty {
5922                self.write_newline();
5923                self.indent_level += 1;
5924                self.write_indent();
5925                self.write_keyword("ON");
5926                self.write_space();
5927                // In pretty mode, split AND conditions onto separate lines
5928                self.generate_join_on_condition(on)?;
5929                self.indent_level -= 1;
5930            } else {
5931                self.write_space();
5932                self.write_keyword("ON");
5933                self.write_space();
5934                self.generate_expression(on)?;
5935            }
5936        }
5937
5938        if !join.using.is_empty() {
5939            if self.config.pretty {
5940                self.write_newline();
5941                self.indent_level += 1;
5942                self.write_indent();
5943                self.write_keyword("USING");
5944                self.write(" (");
5945                for (i, col) in join.using.iter().enumerate() {
5946                    if i > 0 {
5947                        self.write(", ");
5948                    }
5949                    self.generate_identifier(col)?;
5950                }
5951                self.write(")");
5952                self.indent_level -= 1;
5953            } else {
5954                self.write_space();
5955                self.write_keyword("USING");
5956                self.write(" (");
5957                for (i, col) in join.using.iter().enumerate() {
5958                    if i > 0 {
5959                        self.write(", ");
5960                    }
5961                    self.generate_identifier(col)?;
5962                }
5963                self.write(")");
5964            }
5965        }
5966
5967        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5968        for pivot in &join.pivots {
5969            self.write_space();
5970            self.generate_expression(pivot)?;
5971        }
5972
5973        Ok(())
5974    }
5975
5976    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5977    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5978        if let Expression::And(and_op) = expr {
5979            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5980                self.generate_expression(conditions[0])?;
5981                for condition in conditions.iter().skip(1) {
5982                    self.write_newline();
5983                    self.write_indent();
5984                    self.write_keyword("AND");
5985                    self.write_space();
5986                    self.generate_expression(condition)?;
5987                }
5988                return Ok(());
5989            }
5990        }
5991
5992        self.generate_expression(expr)
5993    }
5994
5995    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5996        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5997        self.write("(");
5998        self.generate_expression(&jt.left)?;
5999
6000        // Generate all joins
6001        for join in &jt.joins {
6002            self.generate_join(join)?;
6003        }
6004
6005        // Generate LATERAL VIEW clauses (Hive/Spark)
6006        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6007            self.generate_lateral_view(lv, lv_idx)?;
6008        }
6009
6010        self.write(")");
6011
6012        // Alias
6013        if let Some(alias) = &jt.alias {
6014            self.write_space();
6015            self.write_keyword("AS");
6016            self.write_space();
6017            self.generate_identifier(alias)?;
6018        }
6019
6020        Ok(())
6021    }
6022
6023    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6024        use crate::dialects::DialectType;
6025
6026        if self.config.pretty {
6027            self.write_newline();
6028            self.write_indent();
6029        } else {
6030            self.write_space();
6031        }
6032
6033        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6034        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6035        let use_lateral_join = matches!(
6036            self.config.dialect,
6037            Some(DialectType::PostgreSQL)
6038                | Some(DialectType::DuckDB)
6039                | Some(DialectType::Snowflake)
6040                | Some(DialectType::TSQL)
6041                | Some(DialectType::Presto)
6042                | Some(DialectType::Trino)
6043                | Some(DialectType::Athena)
6044        );
6045
6046        // Check if target dialect should use UNNEST instead of EXPLODE
6047        let use_unnest = matches!(
6048            self.config.dialect,
6049            Some(DialectType::DuckDB)
6050                | Some(DialectType::Presto)
6051                | Some(DialectType::Trino)
6052                | Some(DialectType::Athena)
6053        );
6054
6055        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6056        let (is_posexplode, is_inline, func_args) = match &lv.this {
6057            Expression::Explode(uf) => {
6058                // Expression::Explode is the dedicated EXPLODE expression type
6059                (false, false, vec![uf.this.clone()])
6060            }
6061            Expression::Unnest(uf) => {
6062                let mut args = vec![uf.this.clone()];
6063                args.extend(uf.expressions.clone());
6064                (false, false, args)
6065            }
6066            Expression::Function(func) => {
6067                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6068                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6069                {
6070                    (true, false, func.args.clone())
6071                } else if func.name.eq_ignore_ascii_case("INLINE") {
6072                    (false, true, func.args.clone())
6073                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6074                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6075                {
6076                    (false, false, func.args.clone())
6077                } else {
6078                    (false, false, vec![])
6079                }
6080            }
6081            _ => (false, false, vec![]),
6082        };
6083
6084        if use_lateral_join {
6085            // Convert to CROSS JOIN for PostgreSQL-like dialects
6086            if lv.outer {
6087                self.write_keyword("LEFT JOIN LATERAL");
6088            } else {
6089                self.write_keyword("CROSS JOIN");
6090            }
6091            self.write_space();
6092
6093            if use_unnest && !func_args.is_empty() {
6094                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6095                // For DuckDB, also convert ARRAY(y) -> [y]
6096                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6097                    // DuckDB: ARRAY(y) -> [y]
6098                    func_args
6099                        .iter()
6100                        .map(|a| {
6101                            if let Expression::Function(ref f) = a {
6102                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6103                                    return Expression::ArrayFunc(Box::new(
6104                                        crate::expressions::ArrayConstructor {
6105                                            expressions: f.args.clone(),
6106                                            bracket_notation: true,
6107                                            use_list_keyword: false,
6108                                        },
6109                                    ));
6110                                }
6111                            }
6112                            a.clone()
6113                        })
6114                        .collect::<Vec<_>>()
6115                } else if matches!(
6116                    self.config.dialect,
6117                    Some(DialectType::Presto)
6118                        | Some(DialectType::Trino)
6119                        | Some(DialectType::Athena)
6120                ) {
6121                    // Presto: ARRAY(y) -> ARRAY[y]
6122                    func_args
6123                        .iter()
6124                        .map(|a| {
6125                            if let Expression::Function(ref f) = a {
6126                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6127                                    return Expression::ArrayFunc(Box::new(
6128                                        crate::expressions::ArrayConstructor {
6129                                            expressions: f.args.clone(),
6130                                            bracket_notation: true,
6131                                            use_list_keyword: false,
6132                                        },
6133                                    ));
6134                                }
6135                            }
6136                            a.clone()
6137                        })
6138                        .collect::<Vec<_>>()
6139                } else {
6140                    func_args
6141                };
6142
6143                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6144                if is_posexplode {
6145                    self.write_keyword("LATERAL");
6146                    self.write(" (");
6147                    self.write_keyword("SELECT");
6148                    self.write_space();
6149
6150                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6151                    // column_aliases[0] is the position column, rest are data columns
6152                    let pos_alias = if !lv.column_aliases.is_empty() {
6153                        lv.column_aliases[0].clone()
6154                    } else {
6155                        Identifier::new("pos")
6156                    };
6157                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6158                        lv.column_aliases[1..].to_vec()
6159                    } else {
6160                        vec![Identifier::new("col")]
6161                    };
6162
6163                    // pos - 1 AS pos
6164                    self.generate_identifier(&pos_alias)?;
6165                    self.write(" - 1");
6166                    self.write_space();
6167                    self.write_keyword("AS");
6168                    self.write_space();
6169                    self.generate_identifier(&pos_alias)?;
6170
6171                    // , col [, key, value ...]
6172                    for data_col in &data_aliases {
6173                        self.write(", ");
6174                        self.generate_identifier(data_col)?;
6175                    }
6176
6177                    self.write_space();
6178                    self.write_keyword("FROM");
6179                    self.write_space();
6180                    self.write_keyword("UNNEST");
6181                    self.write("(");
6182                    for (i, arg) in unnest_args.iter().enumerate() {
6183                        if i > 0 {
6184                            self.write(", ");
6185                        }
6186                        self.generate_expression(arg)?;
6187                    }
6188                    self.write(")");
6189                    self.write_space();
6190                    self.write_keyword("WITH ORDINALITY");
6191                    self.write_space();
6192                    self.write_keyword("AS");
6193                    self.write_space();
6194
6195                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6196                    let table_alias_ident = lv
6197                        .table_alias
6198                        .clone()
6199                        .unwrap_or_else(|| Identifier::new("t"));
6200                    self.generate_identifier(&table_alias_ident)?;
6201                    self.write("(");
6202                    for (i, data_col) in data_aliases.iter().enumerate() {
6203                        if i > 0 {
6204                            self.write(", ");
6205                        }
6206                        self.generate_identifier(data_col)?;
6207                    }
6208                    self.write(", ");
6209                    self.generate_identifier(&pos_alias)?;
6210                    self.write("))");
6211                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6212                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6213                    self.write_keyword("LATERAL");
6214                    self.write(" (");
6215                    self.write_keyword("SELECT");
6216                    self.write_space();
6217                    self.write_keyword("UNNEST");
6218                    self.write("(");
6219                    for (i, arg) in unnest_args.iter().enumerate() {
6220                        if i > 0 {
6221                            self.write(", ");
6222                        }
6223                        self.generate_expression(arg)?;
6224                    }
6225                    self.write(", ");
6226                    self.write_keyword("max_depth");
6227                    self.write(" => 2))");
6228
6229                    // Add table and column aliases
6230                    if let Some(alias) = &lv.table_alias {
6231                        self.write_space();
6232                        self.write_keyword("AS");
6233                        self.write_space();
6234                        self.generate_identifier(alias)?;
6235                        if !lv.column_aliases.is_empty() {
6236                            self.write("(");
6237                            for (i, col) in lv.column_aliases.iter().enumerate() {
6238                                if i > 0 {
6239                                    self.write(", ");
6240                                }
6241                                self.generate_identifier(col)?;
6242                            }
6243                            self.write(")");
6244                        }
6245                    } else if !lv.column_aliases.is_empty() {
6246                        // Auto-generate alias like _u_N
6247                        self.write_space();
6248                        self.write_keyword("AS");
6249                        self.write_space();
6250                        self.write(&format!("_u_{}", lv_index));
6251                        self.write("(");
6252                        for (i, col) in lv.column_aliases.iter().enumerate() {
6253                            if i > 0 {
6254                                self.write(", ");
6255                            }
6256                            self.generate_identifier(col)?;
6257                        }
6258                        self.write(")");
6259                    }
6260                } else {
6261                    self.write_keyword("UNNEST");
6262                    self.write("(");
6263                    for (i, arg) in unnest_args.iter().enumerate() {
6264                        if i > 0 {
6265                            self.write(", ");
6266                        }
6267                        self.generate_expression(arg)?;
6268                    }
6269                    self.write(")");
6270
6271                    // Add table and column aliases for non-POSEXPLODE
6272                    if let Some(alias) = &lv.table_alias {
6273                        self.write_space();
6274                        self.write_keyword("AS");
6275                        self.write_space();
6276                        self.generate_identifier(alias)?;
6277                        if !lv.column_aliases.is_empty() {
6278                            self.write("(");
6279                            for (i, col) in lv.column_aliases.iter().enumerate() {
6280                                if i > 0 {
6281                                    self.write(", ");
6282                                }
6283                                self.generate_identifier(col)?;
6284                            }
6285                            self.write(")");
6286                        }
6287                    } else if !lv.column_aliases.is_empty() {
6288                        self.write_space();
6289                        self.write_keyword("AS");
6290                        self.write(" t(");
6291                        for (i, col) in lv.column_aliases.iter().enumerate() {
6292                            if i > 0 {
6293                                self.write(", ");
6294                            }
6295                            self.generate_identifier(col)?;
6296                        }
6297                        self.write(")");
6298                    }
6299                }
6300            } else {
6301                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6302                if !lv.outer {
6303                    self.write_keyword("LATERAL");
6304                    self.write_space();
6305                }
6306                self.generate_expression(&lv.this)?;
6307
6308                // Add table and column aliases
6309                if let Some(alias) = &lv.table_alias {
6310                    self.write_space();
6311                    self.write_keyword("AS");
6312                    self.write_space();
6313                    self.generate_identifier(alias)?;
6314                    if !lv.column_aliases.is_empty() {
6315                        self.write("(");
6316                        for (i, col) in lv.column_aliases.iter().enumerate() {
6317                            if i > 0 {
6318                                self.write(", ");
6319                            }
6320                            self.generate_identifier(col)?;
6321                        }
6322                        self.write(")");
6323                    }
6324                } else if !lv.column_aliases.is_empty() {
6325                    self.write_space();
6326                    self.write_keyword("AS");
6327                    self.write(" t(");
6328                    for (i, col) in lv.column_aliases.iter().enumerate() {
6329                        if i > 0 {
6330                            self.write(", ");
6331                        }
6332                        self.generate_identifier(col)?;
6333                    }
6334                    self.write(")");
6335                }
6336            }
6337
6338            // For LEFT JOIN LATERAL, need ON TRUE
6339            if lv.outer {
6340                self.write_space();
6341                self.write_keyword("ON TRUE");
6342            }
6343        } else {
6344            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6345            self.write_keyword("LATERAL VIEW");
6346            if lv.outer {
6347                self.write_space();
6348                self.write_keyword("OUTER");
6349            }
6350            if self.config.pretty {
6351                self.write_newline();
6352                self.write_indent();
6353            } else {
6354                self.write_space();
6355            }
6356            self.generate_expression(&lv.this)?;
6357
6358            // Table alias
6359            if let Some(alias) = &lv.table_alias {
6360                self.write_space();
6361                self.generate_identifier(alias)?;
6362            }
6363
6364            // Column aliases
6365            if !lv.column_aliases.is_empty() {
6366                self.write_space();
6367                self.write_keyword("AS");
6368                self.write_space();
6369                for (i, col) in lv.column_aliases.iter().enumerate() {
6370                    if i > 0 {
6371                        self.write(", ");
6372                    }
6373                    self.generate_identifier(col)?;
6374                }
6375            }
6376        }
6377
6378        Ok(())
6379    }
6380
6381    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6382        // Collect the left-recursive chain of Union nodes iteratively.
6383        // This avoids stack overflow for deeply nested chains like
6384        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6385        // where the parser builds: Union(Union(Union(A, B), C), D)
6386        let mut chain: Vec<&Union> = vec![outermost];
6387        let mut leftmost: &Expression = &outermost.left;
6388        while let Expression::Union(inner) = leftmost {
6389            chain.push(inner);
6390            leftmost = &inner.left;
6391        }
6392        // chain[0] = outermost, chain[last] = innermost
6393        // leftmost = innermost.left (a non-Union expression, typically Select)
6394
6395        // WITH clause (only on outermost)
6396        if let Some(with) = &outermost.with {
6397            self.generate_with(with)?;
6398            self.write_space();
6399        }
6400
6401        // Generate the base (leftmost) expression
6402        self.generate_expression(leftmost)?;
6403
6404        // Generate each union step from innermost to outermost
6405        for union in chain.iter().rev() {
6406            self.generate_union_step(union)?;
6407        }
6408        Ok(())
6409    }
6410
6411    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6412    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6413        if self.config.pretty {
6414            self.write_newline();
6415            self.write_indent();
6416        } else {
6417            self.write_space();
6418        }
6419
6420        // BigQuery set operation modifiers: [side] [kind] UNION
6421        if let Some(side) = &union.side {
6422            self.write_keyword(side);
6423            self.write_space();
6424        }
6425        if let Some(kind) = &union.kind {
6426            self.write_keyword(kind);
6427            self.write_space();
6428        }
6429
6430        self.write_keyword("UNION");
6431        if union.all {
6432            self.write_space();
6433            self.write_keyword("ALL");
6434        } else if union.distinct {
6435            self.write_space();
6436            self.write_keyword("DISTINCT");
6437        }
6438
6439        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6440        // DuckDB: BY NAME
6441        if union.corresponding || union.by_name {
6442            self.write_space();
6443            self.write_keyword("BY NAME");
6444        }
6445        if !union.on_columns.is_empty() {
6446            self.write_space();
6447            self.write_keyword("ON");
6448            self.write(" (");
6449            for (i, col) in union.on_columns.iter().enumerate() {
6450                if i > 0 {
6451                    self.write(", ");
6452                }
6453                self.generate_expression(col)?;
6454            }
6455            self.write(")");
6456        }
6457
6458        if self.config.pretty {
6459            self.write_newline();
6460            self.write_indent();
6461        } else {
6462            self.write_space();
6463        }
6464        self.generate_expression(&union.right)?;
6465        // ORDER BY, LIMIT, OFFSET for the set operation
6466        if let Some(order_by) = &union.order_by {
6467            if self.config.pretty {
6468                self.write_newline();
6469            } else {
6470                self.write_space();
6471            }
6472            self.write_keyword("ORDER BY");
6473            self.write_space();
6474            for (i, ordered) in order_by.expressions.iter().enumerate() {
6475                if i > 0 {
6476                    self.write(", ");
6477                }
6478                self.generate_ordered(ordered)?;
6479            }
6480        }
6481        if let Some(limit) = &union.limit {
6482            if self.config.pretty {
6483                self.write_newline();
6484            } else {
6485                self.write_space();
6486            }
6487            self.write_keyword("LIMIT");
6488            self.write_space();
6489            self.generate_expression(limit)?;
6490        }
6491        if let Some(offset) = &union.offset {
6492            if self.config.pretty {
6493                self.write_newline();
6494            } else {
6495                self.write_space();
6496            }
6497            self.write_keyword("OFFSET");
6498            self.write_space();
6499            self.generate_expression(offset)?;
6500        }
6501        // DISTRIBUTE BY (Hive/Spark)
6502        if let Some(distribute_by) = &union.distribute_by {
6503            self.write_space();
6504            self.write_keyword("DISTRIBUTE BY");
6505            self.write_space();
6506            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6507                if i > 0 {
6508                    self.write(", ");
6509                }
6510                self.generate_expression(expr)?;
6511            }
6512        }
6513        // SORT BY (Hive/Spark)
6514        if let Some(sort_by) = &union.sort_by {
6515            self.write_space();
6516            self.write_keyword("SORT BY");
6517            self.write_space();
6518            for (i, ord) in sort_by.expressions.iter().enumerate() {
6519                if i > 0 {
6520                    self.write(", ");
6521                }
6522                self.generate_ordered(ord)?;
6523            }
6524        }
6525        // CLUSTER BY (Hive/Spark)
6526        if let Some(cluster_by) = &union.cluster_by {
6527            self.write_space();
6528            self.write_keyword("CLUSTER BY");
6529            self.write_space();
6530            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6531                if i > 0 {
6532                    self.write(", ");
6533                }
6534                self.generate_ordered(ord)?;
6535            }
6536        }
6537        Ok(())
6538    }
6539
6540    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6541        // Collect the left-recursive chain iteratively to avoid stack overflow
6542        let mut chain: Vec<&Intersect> = vec![outermost];
6543        let mut leftmost: &Expression = &outermost.left;
6544        while let Expression::Intersect(inner) = leftmost {
6545            chain.push(inner);
6546            leftmost = &inner.left;
6547        }
6548
6549        if let Some(with) = &outermost.with {
6550            self.generate_with(with)?;
6551            self.write_space();
6552        }
6553
6554        self.generate_expression(leftmost)?;
6555
6556        for intersect in chain.iter().rev() {
6557            self.generate_intersect_step(intersect)?;
6558        }
6559        Ok(())
6560    }
6561
6562    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6563    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6564        if self.config.pretty {
6565            self.write_newline();
6566            self.write_indent();
6567        } else {
6568            self.write_space();
6569        }
6570
6571        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6572        if let Some(side) = &intersect.side {
6573            self.write_keyword(side);
6574            self.write_space();
6575        }
6576        if let Some(kind) = &intersect.kind {
6577            self.write_keyword(kind);
6578            self.write_space();
6579        }
6580
6581        self.write_keyword("INTERSECT");
6582        if intersect.all {
6583            self.write_space();
6584            self.write_keyword("ALL");
6585        } else if intersect.distinct {
6586            self.write_space();
6587            self.write_keyword("DISTINCT");
6588        }
6589
6590        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6591        // DuckDB: BY NAME
6592        if intersect.corresponding || intersect.by_name {
6593            self.write_space();
6594            self.write_keyword("BY NAME");
6595        }
6596        if !intersect.on_columns.is_empty() {
6597            self.write_space();
6598            self.write_keyword("ON");
6599            self.write(" (");
6600            for (i, col) in intersect.on_columns.iter().enumerate() {
6601                if i > 0 {
6602                    self.write(", ");
6603                }
6604                self.generate_expression(col)?;
6605            }
6606            self.write(")");
6607        }
6608
6609        if self.config.pretty {
6610            self.write_newline();
6611            self.write_indent();
6612        } else {
6613            self.write_space();
6614        }
6615        self.generate_expression(&intersect.right)?;
6616        // ORDER BY, LIMIT, OFFSET for the set operation
6617        if let Some(order_by) = &intersect.order_by {
6618            if self.config.pretty {
6619                self.write_newline();
6620            } else {
6621                self.write_space();
6622            }
6623            self.write_keyword("ORDER BY");
6624            self.write_space();
6625            for (i, ordered) in order_by.expressions.iter().enumerate() {
6626                if i > 0 {
6627                    self.write(", ");
6628                }
6629                self.generate_ordered(ordered)?;
6630            }
6631        }
6632        if let Some(limit) = &intersect.limit {
6633            if self.config.pretty {
6634                self.write_newline();
6635            } else {
6636                self.write_space();
6637            }
6638            self.write_keyword("LIMIT");
6639            self.write_space();
6640            self.generate_expression(limit)?;
6641        }
6642        if let Some(offset) = &intersect.offset {
6643            if self.config.pretty {
6644                self.write_newline();
6645            } else {
6646                self.write_space();
6647            }
6648            self.write_keyword("OFFSET");
6649            self.write_space();
6650            self.generate_expression(offset)?;
6651        }
6652        // DISTRIBUTE BY (Hive/Spark)
6653        if let Some(distribute_by) = &intersect.distribute_by {
6654            self.write_space();
6655            self.write_keyword("DISTRIBUTE BY");
6656            self.write_space();
6657            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6658                if i > 0 {
6659                    self.write(", ");
6660                }
6661                self.generate_expression(expr)?;
6662            }
6663        }
6664        // SORT BY (Hive/Spark)
6665        if let Some(sort_by) = &intersect.sort_by {
6666            self.write_space();
6667            self.write_keyword("SORT BY");
6668            self.write_space();
6669            for (i, ord) in sort_by.expressions.iter().enumerate() {
6670                if i > 0 {
6671                    self.write(", ");
6672                }
6673                self.generate_ordered(ord)?;
6674            }
6675        }
6676        // CLUSTER BY (Hive/Spark)
6677        if let Some(cluster_by) = &intersect.cluster_by {
6678            self.write_space();
6679            self.write_keyword("CLUSTER BY");
6680            self.write_space();
6681            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6682                if i > 0 {
6683                    self.write(", ");
6684                }
6685                self.generate_ordered(ord)?;
6686            }
6687        }
6688        Ok(())
6689    }
6690
6691    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6692        // Collect the left-recursive chain iteratively to avoid stack overflow
6693        let mut chain: Vec<&Except> = vec![outermost];
6694        let mut leftmost: &Expression = &outermost.left;
6695        while let Expression::Except(inner) = leftmost {
6696            chain.push(inner);
6697            leftmost = &inner.left;
6698        }
6699
6700        if let Some(with) = &outermost.with {
6701            self.generate_with(with)?;
6702            self.write_space();
6703        }
6704
6705        self.generate_expression(leftmost)?;
6706
6707        for except in chain.iter().rev() {
6708            self.generate_except_step(except)?;
6709        }
6710        Ok(())
6711    }
6712
6713    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6714    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6715        use crate::dialects::DialectType;
6716
6717        if self.config.pretty {
6718            self.write_newline();
6719            self.write_indent();
6720        } else {
6721            self.write_space();
6722        }
6723
6724        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6725        if let Some(side) = &except.side {
6726            self.write_keyword(side);
6727            self.write_space();
6728        }
6729        if let Some(kind) = &except.kind {
6730            self.write_keyword(kind);
6731            self.write_space();
6732        }
6733
6734        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6735        match self.config.dialect {
6736            Some(DialectType::Oracle) if !except.all => {
6737                self.write_keyword("MINUS");
6738            }
6739            Some(DialectType::ClickHouse) => {
6740                self.write_keyword("EXCEPT");
6741                let preserve_all = self.config.source_dialect.is_none()
6742                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
6743                if except.all && preserve_all {
6744                    self.write_space();
6745                    self.write_keyword("ALL");
6746                }
6747                if except.distinct {
6748                    self.write_space();
6749                    self.write_keyword("DISTINCT");
6750                }
6751            }
6752            Some(DialectType::BigQuery) => {
6753                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6754                self.write_keyword("EXCEPT");
6755                if except.all {
6756                    self.write_space();
6757                    self.write_keyword("ALL");
6758                } else {
6759                    self.write_space();
6760                    self.write_keyword("DISTINCT");
6761                }
6762            }
6763            _ => {
6764                self.write_keyword("EXCEPT");
6765                if except.all {
6766                    self.write_space();
6767                    self.write_keyword("ALL");
6768                } else if except.distinct {
6769                    self.write_space();
6770                    self.write_keyword("DISTINCT");
6771                }
6772            }
6773        }
6774
6775        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6776        // DuckDB: BY NAME
6777        if except.corresponding || except.by_name {
6778            self.write_space();
6779            self.write_keyword("BY NAME");
6780        }
6781        if !except.on_columns.is_empty() {
6782            self.write_space();
6783            self.write_keyword("ON");
6784            self.write(" (");
6785            for (i, col) in except.on_columns.iter().enumerate() {
6786                if i > 0 {
6787                    self.write(", ");
6788                }
6789                self.generate_expression(col)?;
6790            }
6791            self.write(")");
6792        }
6793
6794        if self.config.pretty {
6795            self.write_newline();
6796            self.write_indent();
6797        } else {
6798            self.write_space();
6799        }
6800        self.generate_expression(&except.right)?;
6801        // ORDER BY, LIMIT, OFFSET for the set operation
6802        if let Some(order_by) = &except.order_by {
6803            if self.config.pretty {
6804                self.write_newline();
6805            } else {
6806                self.write_space();
6807            }
6808            self.write_keyword("ORDER BY");
6809            self.write_space();
6810            for (i, ordered) in order_by.expressions.iter().enumerate() {
6811                if i > 0 {
6812                    self.write(", ");
6813                }
6814                self.generate_ordered(ordered)?;
6815            }
6816        }
6817        if let Some(limit) = &except.limit {
6818            if self.config.pretty {
6819                self.write_newline();
6820            } else {
6821                self.write_space();
6822            }
6823            self.write_keyword("LIMIT");
6824            self.write_space();
6825            self.generate_expression(limit)?;
6826        }
6827        if let Some(offset) = &except.offset {
6828            if self.config.pretty {
6829                self.write_newline();
6830            } else {
6831                self.write_space();
6832            }
6833            self.write_keyword("OFFSET");
6834            self.write_space();
6835            self.generate_expression(offset)?;
6836        }
6837        // DISTRIBUTE BY (Hive/Spark)
6838        if let Some(distribute_by) = &except.distribute_by {
6839            self.write_space();
6840            self.write_keyword("DISTRIBUTE BY");
6841            self.write_space();
6842            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6843                if i > 0 {
6844                    self.write(", ");
6845                }
6846                self.generate_expression(expr)?;
6847            }
6848        }
6849        // SORT BY (Hive/Spark)
6850        if let Some(sort_by) = &except.sort_by {
6851            self.write_space();
6852            self.write_keyword("SORT BY");
6853            self.write_space();
6854            for (i, ord) in sort_by.expressions.iter().enumerate() {
6855                if i > 0 {
6856                    self.write(", ");
6857                }
6858                self.generate_ordered(ord)?;
6859            }
6860        }
6861        // CLUSTER BY (Hive/Spark)
6862        if let Some(cluster_by) = &except.cluster_by {
6863            self.write_space();
6864            self.write_keyword("CLUSTER BY");
6865            self.write_space();
6866            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6867                if i > 0 {
6868                    self.write(", ");
6869                }
6870                self.generate_ordered(ord)?;
6871            }
6872        }
6873        Ok(())
6874    }
6875
6876    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6877        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6878        let prepend_query_cte = if insert.with.is_none() {
6879            use crate::dialects::DialectType;
6880            let should_prepend = matches!(
6881                self.config.dialect,
6882                Some(DialectType::TSQL)
6883                    | Some(DialectType::Fabric)
6884                    | Some(DialectType::Spark)
6885                    | Some(DialectType::Databricks)
6886                    | Some(DialectType::Hive)
6887            );
6888            if should_prepend {
6889                if let Some(Expression::Select(select)) = &insert.query {
6890                    select.with.clone()
6891                } else {
6892                    None
6893                }
6894            } else {
6895                None
6896            }
6897        } else {
6898            None
6899        };
6900
6901        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6902        if let Some(with) = &insert.with {
6903            self.generate_with(with)?;
6904            self.write_space();
6905        } else if let Some(with) = &prepend_query_cte {
6906            self.generate_with(with)?;
6907            self.write_space();
6908        }
6909
6910        // Output leading comments before INSERT
6911        for comment in &insert.leading_comments {
6912            self.write_formatted_comment(comment);
6913            self.write(" ");
6914        }
6915
6916        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6917        if let Some(dir) = &insert.directory {
6918            self.write_keyword("INSERT OVERWRITE");
6919            if dir.local {
6920                self.write_space();
6921                self.write_keyword("LOCAL");
6922            }
6923            self.write_space();
6924            self.write_keyword("DIRECTORY");
6925            self.write_space();
6926            self.write("'");
6927            self.write(&dir.path);
6928            self.write("'");
6929
6930            // ROW FORMAT clause
6931            if let Some(row_format) = &dir.row_format {
6932                self.write_space();
6933                self.write_keyword("ROW FORMAT");
6934                if row_format.delimited {
6935                    self.write_space();
6936                    self.write_keyword("DELIMITED");
6937                }
6938                if let Some(val) = &row_format.fields_terminated_by {
6939                    self.write_space();
6940                    self.write_keyword("FIELDS TERMINATED BY");
6941                    self.write_space();
6942                    self.generate_string_literal(val)?;
6943                }
6944                if let Some(val) = &row_format.collection_items_terminated_by {
6945                    self.write_space();
6946                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6947                    self.write_space();
6948                    self.write("'");
6949                    self.write(val);
6950                    self.write("'");
6951                }
6952                if let Some(val) = &row_format.map_keys_terminated_by {
6953                    self.write_space();
6954                    self.write_keyword("MAP KEYS TERMINATED BY");
6955                    self.write_space();
6956                    self.write("'");
6957                    self.write(val);
6958                    self.write("'");
6959                }
6960                if let Some(val) = &row_format.lines_terminated_by {
6961                    self.write_space();
6962                    self.write_keyword("LINES TERMINATED BY");
6963                    self.write_space();
6964                    self.write("'");
6965                    self.write(val);
6966                    self.write("'");
6967                }
6968                if let Some(val) = &row_format.null_defined_as {
6969                    self.write_space();
6970                    self.write_keyword("NULL DEFINED AS");
6971                    self.write_space();
6972                    self.write("'");
6973                    self.write(val);
6974                    self.write("'");
6975                }
6976            }
6977
6978            // STORED AS clause
6979            if let Some(format) = &dir.stored_as {
6980                self.write_space();
6981                self.write_keyword("STORED AS");
6982                self.write_space();
6983                self.write_keyword(format);
6984            }
6985
6986            // Query (SELECT statement)
6987            if let Some(query) = &insert.query {
6988                self.write_space();
6989                self.generate_expression(query)?;
6990            }
6991
6992            return Ok(());
6993        }
6994
6995        if insert.is_replace {
6996            // MySQL/SQLite REPLACE INTO statement
6997            self.write_keyword("REPLACE INTO");
6998        } else if insert.overwrite {
6999            // Use dialect-specific INSERT OVERWRITE format
7000            self.write_keyword("INSERT");
7001            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7002            if let Some(ref hint) = insert.hint {
7003                self.generate_hint(hint)?;
7004            }
7005            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7006        } else if let Some(ref action) = insert.conflict_action {
7007            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7008            self.write_keyword("INSERT OR");
7009            self.write_space();
7010            self.write_keyword(action);
7011            self.write_space();
7012            self.write_keyword("INTO");
7013        } else if insert.ignore {
7014            // MySQL INSERT IGNORE syntax
7015            self.write_keyword("INSERT IGNORE INTO");
7016        } else {
7017            self.write_keyword("INSERT");
7018            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7019            if let Some(ref hint) = insert.hint {
7020                self.generate_hint(hint)?;
7021            }
7022            self.write_space();
7023            self.write_keyword("INTO");
7024        }
7025        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7026        if let Some(ref func) = insert.function_target {
7027            self.write_space();
7028            self.write_keyword("FUNCTION");
7029            self.write_space();
7030            self.generate_expression(func)?;
7031        } else {
7032            self.write_space();
7033            self.generate_table(&insert.table)?;
7034        }
7035
7036        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7037        if let Some(ref alias) = insert.alias {
7038            self.write_space();
7039            if insert.alias_explicit_as {
7040                self.write_keyword("AS");
7041                self.write_space();
7042            }
7043            self.generate_identifier(alias)?;
7044        }
7045
7046        // IF EXISTS clause (Hive)
7047        if insert.if_exists {
7048            self.write_space();
7049            self.write_keyword("IF EXISTS");
7050        }
7051
7052        // REPLACE WHERE clause (Databricks)
7053        if let Some(ref replace_where) = insert.replace_where {
7054            if self.config.pretty {
7055                self.write_newline();
7056                self.write_indent();
7057            } else {
7058                self.write_space();
7059            }
7060            self.write_keyword("REPLACE WHERE");
7061            self.write_space();
7062            self.generate_expression(replace_where)?;
7063        }
7064
7065        // Generate PARTITION clause if present
7066        if !insert.partition.is_empty() {
7067            self.write_space();
7068            self.write_keyword("PARTITION");
7069            self.write("(");
7070            for (i, (col, val)) in insert.partition.iter().enumerate() {
7071                if i > 0 {
7072                    self.write(", ");
7073                }
7074                self.generate_identifier(col)?;
7075                if let Some(v) = val {
7076                    self.write(" = ");
7077                    self.generate_expression(v)?;
7078                }
7079            }
7080            self.write(")");
7081        }
7082
7083        // ClickHouse: PARTITION BY expr
7084        if let Some(ref partition_by) = insert.partition_by {
7085            self.write_space();
7086            self.write_keyword("PARTITION BY");
7087            self.write_space();
7088            self.generate_expression(partition_by)?;
7089        }
7090
7091        // ClickHouse: SETTINGS key = val, ...
7092        if !insert.settings.is_empty() {
7093            self.write_space();
7094            self.write_keyword("SETTINGS");
7095            self.write_space();
7096            for (i, setting) in insert.settings.iter().enumerate() {
7097                if i > 0 {
7098                    self.write(", ");
7099                }
7100                self.generate_expression(setting)?;
7101            }
7102        }
7103
7104        if !insert.columns.is_empty() {
7105            if insert.alias.is_some() && insert.alias_explicit_as {
7106                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7107                self.write("(");
7108            } else {
7109                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7110                self.write(" (");
7111            }
7112            for (i, col) in insert.columns.iter().enumerate() {
7113                if i > 0 {
7114                    self.write(", ");
7115                }
7116                self.generate_identifier(col)?;
7117            }
7118            self.write(")");
7119        }
7120
7121        // OUTPUT clause (TSQL)
7122        if let Some(ref output) = insert.output {
7123            self.generate_output_clause(output)?;
7124        }
7125
7126        // BY NAME modifier (DuckDB)
7127        if insert.by_name {
7128            self.write_space();
7129            self.write_keyword("BY NAME");
7130        }
7131
7132        if insert.default_values {
7133            self.write_space();
7134            self.write_keyword("DEFAULT VALUES");
7135        } else if let Some(query) = &insert.query {
7136            if self.config.pretty {
7137                self.write_newline();
7138            } else {
7139                self.write_space();
7140            }
7141            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7142            if prepend_query_cte.is_some() {
7143                if let Expression::Select(select) = query {
7144                    let mut select_no_with = select.clone();
7145                    select_no_with.with = None;
7146                    self.generate_select(&select_no_with)?;
7147                } else {
7148                    self.generate_expression(query)?;
7149                }
7150            } else {
7151                self.generate_expression(query)?;
7152            }
7153        } else if !insert.values.is_empty() {
7154            if self.config.pretty {
7155                // Pretty printing: VALUES on new line, each tuple indented
7156                self.write_newline();
7157                self.write_keyword("VALUES");
7158                self.write_newline();
7159                self.indent_level += 1;
7160                for (i, row) in insert.values.iter().enumerate() {
7161                    if i > 0 {
7162                        self.write(",");
7163                        self.write_newline();
7164                    }
7165                    self.write_indent();
7166                    self.write("(");
7167                    for (j, val) in row.iter().enumerate() {
7168                        if j > 0 {
7169                            self.write(", ");
7170                        }
7171                        self.generate_expression(val)?;
7172                    }
7173                    self.write(")");
7174                }
7175                self.indent_level -= 1;
7176            } else {
7177                // Non-pretty: single line
7178                self.write_space();
7179                self.write_keyword("VALUES");
7180                for (i, row) in insert.values.iter().enumerate() {
7181                    if i > 0 {
7182                        self.write(",");
7183                    }
7184                    self.write(" (");
7185                    for (j, val) in row.iter().enumerate() {
7186                        if j > 0 {
7187                            self.write(", ");
7188                        }
7189                        self.generate_expression(val)?;
7190                    }
7191                    self.write(")");
7192                }
7193            }
7194        }
7195
7196        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7197        if let Some(ref source) = insert.source {
7198            self.write_space();
7199            self.write_keyword("TABLE");
7200            self.write_space();
7201            self.generate_expression(source)?;
7202        }
7203
7204        // Source alias (MySQL: VALUES (...) AS new_data)
7205        if let Some(alias) = &insert.source_alias {
7206            self.write_space();
7207            self.write_keyword("AS");
7208            self.write_space();
7209            self.generate_identifier(alias)?;
7210        }
7211
7212        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7213        if let Some(on_conflict) = &insert.on_conflict {
7214            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7215                self.write_space();
7216                self.generate_expression(on_conflict)?;
7217            }
7218        }
7219
7220        // RETURNING clause
7221        if !insert.returning.is_empty() {
7222            self.write_space();
7223            self.write_keyword("RETURNING");
7224            self.write_space();
7225            for (i, expr) in insert.returning.iter().enumerate() {
7226                if i > 0 {
7227                    self.write(", ");
7228                }
7229                self.generate_expression(expr)?;
7230            }
7231        }
7232
7233        Ok(())
7234    }
7235
7236    fn generate_update(&mut self, update: &Update) -> Result<()> {
7237        // Output leading comments before UPDATE
7238        for comment in &update.leading_comments {
7239            self.write_formatted_comment(comment);
7240            self.write(" ");
7241        }
7242
7243        // WITH clause (CTEs)
7244        if let Some(ref with) = update.with {
7245            self.generate_with(with)?;
7246            self.write_space();
7247        }
7248
7249        self.write_keyword("UPDATE");
7250        if let Some(hint) = &update.hint {
7251            self.generate_hint(hint)?;
7252        }
7253        self.write_space();
7254        self.generate_table(&update.table)?;
7255
7256        let mysql_like_update_from = matches!(
7257            self.config.dialect,
7258            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7259        ) && update.from_clause.is_some();
7260
7261        let mut set_pairs = update.set.clone();
7262
7263        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7264        let mut pre_set_joins = update.table_joins.clone();
7265        if mysql_like_update_from {
7266            let target_name = update
7267                .table
7268                .alias
7269                .as_ref()
7270                .map(|a| a.name.clone())
7271                .unwrap_or_else(|| update.table.name.name.clone());
7272
7273            for (col, _) in &mut set_pairs {
7274                if !col.name.contains('.') {
7275                    col.name = format!("{}.{}", target_name, col.name);
7276                }
7277            }
7278
7279            if let Some(from_clause) = &update.from_clause {
7280                for table_expr in &from_clause.expressions {
7281                    pre_set_joins.push(crate::expressions::Join {
7282                        this: table_expr.clone(),
7283                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7284                            value: true,
7285                        })),
7286                        using: Vec::new(),
7287                        kind: crate::expressions::JoinKind::Inner,
7288                        use_inner_keyword: false,
7289                        use_outer_keyword: false,
7290                        deferred_condition: false,
7291                        join_hint: None,
7292                        match_condition: None,
7293                        pivots: Vec::new(),
7294                        comments: Vec::new(),
7295                        nesting_group: 0,
7296                        directed: false,
7297                    });
7298                }
7299            }
7300            for join in &update.from_joins {
7301                let mut join = join.clone();
7302                if join.on.is_none() && join.using.is_empty() {
7303                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7304                        value: true,
7305                    }));
7306                }
7307                pre_set_joins.push(join);
7308            }
7309        }
7310
7311        // Extra tables for multi-table UPDATE (MySQL syntax)
7312        for extra_table in &update.extra_tables {
7313            self.write(", ");
7314            self.generate_table(extra_table)?;
7315        }
7316
7317        // JOINs attached to the table list (MySQL multi-table syntax)
7318        for join in &pre_set_joins {
7319            // generate_join already adds a leading space
7320            self.generate_join(join)?;
7321        }
7322
7323        // Teradata: FROM clause comes before SET
7324        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7325        if teradata_from_before_set && !mysql_like_update_from {
7326            if let Some(ref from_clause) = update.from_clause {
7327                self.write_space();
7328                self.write_keyword("FROM");
7329                self.write_space();
7330                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7331                    if i > 0 {
7332                        self.write(", ");
7333                    }
7334                    self.generate_expression(table_expr)?;
7335                }
7336            }
7337            for join in &update.from_joins {
7338                self.generate_join(join)?;
7339            }
7340        }
7341
7342        self.write_space();
7343        self.write_keyword("SET");
7344        self.write_space();
7345
7346        for (i, (col, val)) in set_pairs.iter().enumerate() {
7347            if i > 0 {
7348                self.write(", ");
7349            }
7350            self.generate_identifier(col)?;
7351            self.write(" = ");
7352            self.generate_expression(val)?;
7353        }
7354
7355        // OUTPUT clause (TSQL)
7356        if let Some(ref output) = update.output {
7357            self.generate_output_clause(output)?;
7358        }
7359
7360        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7361        if !mysql_like_update_from && !teradata_from_before_set {
7362            if let Some(ref from_clause) = update.from_clause {
7363                self.write_space();
7364                self.write_keyword("FROM");
7365                self.write_space();
7366                // Generate each table in the FROM clause
7367                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7368                    if i > 0 {
7369                        self.write(", ");
7370                    }
7371                    self.generate_expression(table_expr)?;
7372                }
7373            }
7374        }
7375
7376        if !mysql_like_update_from && !teradata_from_before_set {
7377            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7378            for join in &update.from_joins {
7379                self.generate_join(join)?;
7380            }
7381        }
7382
7383        if let Some(where_clause) = &update.where_clause {
7384            self.write_space();
7385            self.write_keyword("WHERE");
7386            self.write_space();
7387            self.generate_expression(&where_clause.this)?;
7388        }
7389
7390        // RETURNING clause
7391        if !update.returning.is_empty() {
7392            self.write_space();
7393            self.write_keyword("RETURNING");
7394            self.write_space();
7395            for (i, expr) in update.returning.iter().enumerate() {
7396                if i > 0 {
7397                    self.write(", ");
7398                }
7399                self.generate_expression(expr)?;
7400            }
7401        }
7402
7403        // ORDER BY clause (MySQL)
7404        if let Some(ref order_by) = update.order_by {
7405            self.write_space();
7406            self.generate_order_by(order_by)?;
7407        }
7408
7409        // LIMIT clause (MySQL)
7410        if let Some(ref limit) = update.limit {
7411            self.write_space();
7412            self.write_keyword("LIMIT");
7413            self.write_space();
7414            self.generate_expression(limit)?;
7415        }
7416
7417        Ok(())
7418    }
7419
7420    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7421        // Output WITH clause if present
7422        if let Some(with) = &delete.with {
7423            self.generate_with(with)?;
7424            self.write_space();
7425        }
7426
7427        // Output leading comments before DELETE
7428        for comment in &delete.leading_comments {
7429            self.write_formatted_comment(comment);
7430            self.write(" ");
7431        }
7432
7433        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7434        if !delete.tables.is_empty() && !delete.tables_from_using {
7435            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7436            self.write_keyword("DELETE");
7437            if let Some(hint) = &delete.hint {
7438                self.generate_hint(hint)?;
7439            }
7440            self.write_space();
7441            for (i, tbl) in delete.tables.iter().enumerate() {
7442                if i > 0 {
7443                    self.write(", ");
7444                }
7445                self.generate_table(tbl)?;
7446            }
7447            // TSQL: OUTPUT clause between target table and FROM
7448            if let Some(ref output) = delete.output {
7449                self.generate_output_clause(output)?;
7450            }
7451            self.write_space();
7452            self.write_keyword("FROM");
7453            self.write_space();
7454            self.generate_table(&delete.table)?;
7455        } else if !delete.tables.is_empty() && delete.tables_from_using {
7456            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7457            self.write_keyword("DELETE");
7458            if let Some(hint) = &delete.hint {
7459                self.generate_hint(hint)?;
7460            }
7461            self.write_space();
7462            self.write_keyword("FROM");
7463            self.write_space();
7464            for (i, tbl) in delete.tables.iter().enumerate() {
7465                if i > 0 {
7466                    self.write(", ");
7467                }
7468                self.generate_table(tbl)?;
7469            }
7470        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7471            // BigQuery-style DELETE without FROM keyword
7472            self.write_keyword("DELETE");
7473            if let Some(hint) = &delete.hint {
7474                self.generate_hint(hint)?;
7475            }
7476            self.write_space();
7477            self.generate_table(&delete.table)?;
7478        } else {
7479            self.write_keyword("DELETE");
7480            if let Some(hint) = &delete.hint {
7481                self.generate_hint(hint)?;
7482            }
7483            self.write_space();
7484            self.write_keyword("FROM");
7485            self.write_space();
7486            self.generate_table(&delete.table)?;
7487        }
7488
7489        // ClickHouse: ON CLUSTER clause
7490        if let Some(ref on_cluster) = delete.on_cluster {
7491            self.write_space();
7492            self.generate_on_cluster(on_cluster)?;
7493        }
7494
7495        // FORCE INDEX hint (MySQL)
7496        if let Some(ref idx) = delete.force_index {
7497            self.write_space();
7498            self.write_keyword("FORCE INDEX");
7499            self.write(" (");
7500            self.write(idx);
7501            self.write(")");
7502        }
7503
7504        // Optional alias
7505        if let Some(ref alias) = delete.alias {
7506            self.write_space();
7507            if delete.alias_explicit_as
7508                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7509            {
7510                self.write_keyword("AS");
7511                self.write_space();
7512            }
7513            self.generate_identifier(alias)?;
7514        }
7515
7516        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7517        if !delete.tables_from_using {
7518            for join in &delete.joins {
7519                self.generate_join(join)?;
7520            }
7521        }
7522
7523        // USING clause (PostgreSQL/DuckDB/MySQL)
7524        if !delete.using.is_empty() {
7525            self.write_space();
7526            self.write_keyword("USING");
7527            for (i, table) in delete.using.iter().enumerate() {
7528                if i > 0 {
7529                    self.write(",");
7530                }
7531                self.write_space();
7532                // Check if the table has subquery hints (DuckDB USING with subquery)
7533                if !table.hints.is_empty() && table.name.is_empty() {
7534                    // Subquery in USING: (VALUES ...) AS alias(cols)
7535                    self.generate_expression(&table.hints[0])?;
7536                    if let Some(ref alias) = table.alias {
7537                        self.write_space();
7538                        if table.alias_explicit_as {
7539                            self.write_keyword("AS");
7540                            self.write_space();
7541                        }
7542                        self.generate_identifier(alias)?;
7543                        if !table.column_aliases.is_empty() {
7544                            self.write("(");
7545                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7546                                if j > 0 {
7547                                    self.write(", ");
7548                                }
7549                                self.generate_identifier(col_alias)?;
7550                            }
7551                            self.write(")");
7552                        }
7553                    }
7554                } else {
7555                    self.generate_table(table)?;
7556                }
7557            }
7558        }
7559
7560        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7561        if delete.tables_from_using {
7562            for join in &delete.joins {
7563                self.generate_join(join)?;
7564            }
7565        }
7566
7567        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7568        let output_already_emitted =
7569            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7570        if !output_already_emitted {
7571            if let Some(ref output) = delete.output {
7572                self.generate_output_clause(output)?;
7573            }
7574        }
7575
7576        if let Some(where_clause) = &delete.where_clause {
7577            self.write_space();
7578            self.write_keyword("WHERE");
7579            self.write_space();
7580            self.generate_expression(&where_clause.this)?;
7581        }
7582
7583        // ORDER BY clause (MySQL)
7584        if let Some(ref order_by) = delete.order_by {
7585            self.write_space();
7586            self.generate_order_by(order_by)?;
7587        }
7588
7589        // LIMIT clause (MySQL)
7590        if let Some(ref limit) = delete.limit {
7591            self.write_space();
7592            self.write_keyword("LIMIT");
7593            self.write_space();
7594            self.generate_expression(limit)?;
7595        }
7596
7597        // RETURNING clause (PostgreSQL)
7598        if !delete.returning.is_empty() {
7599            self.write_space();
7600            self.write_keyword("RETURNING");
7601            self.write_space();
7602            for (i, expr) in delete.returning.iter().enumerate() {
7603                if i > 0 {
7604                    self.write(", ");
7605                }
7606                self.generate_expression(expr)?;
7607            }
7608        }
7609
7610        Ok(())
7611    }
7612
7613    // ==================== DDL Generation ====================
7614
7615    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7616        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7617        // CREATE TABLE AS SELECT uses Trino (double quotes)
7618        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7619        let saved_athena_hive_context = self.athena_hive_context;
7620        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7621        if matches!(
7622            self.config.dialect,
7623            Some(crate::dialects::DialectType::Athena)
7624        ) {
7625            // Use Hive context if:
7626            // 1. It's an EXTERNAL table, OR
7627            // 2. There's no AS SELECT clause
7628            let is_external = ct
7629                .table_modifier
7630                .as_ref()
7631                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7632                .unwrap_or(false);
7633            let has_as_select = ct.as_select.is_some();
7634            self.athena_hive_context = is_external || !has_as_select;
7635        }
7636
7637        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7638        if matches!(
7639            self.config.dialect,
7640            Some(crate::dialects::DialectType::TSQL)
7641        ) {
7642            if let Some(ref query) = ct.as_select {
7643                // Output WITH CTE clause if present
7644                if let Some(with_cte) = &ct.with_cte {
7645                    self.generate_with(with_cte)?;
7646                    self.write_space();
7647                }
7648
7649                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7650                self.write_keyword("SELECT");
7651                self.write(" * ");
7652                self.write_keyword("INTO");
7653                self.write_space();
7654
7655                // If temporary, prefix with # for TSQL temp table
7656                if ct.temporary {
7657                    self.write("#");
7658                }
7659                self.generate_table(&ct.name)?;
7660
7661                self.write_space();
7662                self.write_keyword("FROM");
7663                self.write(" (");
7664                // For TSQL, add aliases to select columns to preserve column names
7665                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7666                self.generate_expression(&aliased_query)?;
7667                self.write(") ");
7668                self.write_keyword("AS");
7669                self.write(" temp");
7670                return Ok(());
7671            }
7672        }
7673
7674        // Output WITH CTE clause if present
7675        if let Some(with_cte) = &ct.with_cte {
7676            self.generate_with(with_cte)?;
7677            self.write_space();
7678        }
7679
7680        // Output leading comments before CREATE
7681        for comment in &ct.leading_comments {
7682            self.write_formatted_comment(comment);
7683            self.write(" ");
7684        }
7685        self.write_keyword("CREATE");
7686
7687        if ct.or_replace {
7688            self.write_space();
7689            self.write_keyword("OR REPLACE");
7690        }
7691
7692        if ct.temporary {
7693            self.write_space();
7694            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7695            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7696                self.write_keyword("GLOBAL TEMPORARY");
7697            } else {
7698                self.write_keyword("TEMPORARY");
7699            }
7700        }
7701
7702        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7703        let is_dictionary = ct
7704            .table_modifier
7705            .as_ref()
7706            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7707            .unwrap_or(false);
7708        if let Some(ref modifier) = ct.table_modifier {
7709            // TRANSIENT is Snowflake-specific - skip for other dialects
7710            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7711                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7712            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7713            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7714                || modifier.eq_ignore_ascii_case("SET")
7715                || modifier.eq_ignore_ascii_case("MULTISET")
7716                || modifier.to_ascii_uppercase().contains("VOLATILE")
7717                || modifier.to_ascii_uppercase().starts_with("SET ")
7718                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7719            let skip_teradata =
7720                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7721            if !skip_transient && !skip_teradata {
7722                self.write_space();
7723                self.write_keyword(modifier);
7724            }
7725        }
7726
7727        if !is_dictionary {
7728            self.write_space();
7729            self.write_keyword("TABLE");
7730        }
7731
7732        if ct.if_not_exists {
7733            self.write_space();
7734            self.write_keyword("IF NOT EXISTS");
7735        }
7736
7737        self.write_space();
7738        self.generate_table(&ct.name)?;
7739
7740        // ClickHouse: UUID 'xxx' clause after table name
7741        if let Some(ref uuid) = ct.uuid {
7742            self.write_space();
7743            self.write_keyword("UUID");
7744            self.write(" '");
7745            self.write(uuid);
7746            self.write("'");
7747        }
7748
7749        // ClickHouse: ON CLUSTER clause
7750        if let Some(ref on_cluster) = ct.on_cluster {
7751            self.write_space();
7752            self.generate_on_cluster(on_cluster)?;
7753        }
7754
7755        // Teradata: options after table name before column list (comma-separated)
7756        if matches!(
7757            self.config.dialect,
7758            Some(crate::dialects::DialectType::Teradata)
7759        ) && !ct.teradata_post_name_options.is_empty()
7760        {
7761            for opt in &ct.teradata_post_name_options {
7762                self.write(", ");
7763                self.write(opt);
7764            }
7765        }
7766
7767        // Snowflake: COPY GRANTS clause
7768        if ct.copy_grants {
7769            self.write_space();
7770            self.write_keyword("COPY GRANTS");
7771        }
7772
7773        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7774        if let Some(ref using_template) = ct.using_template {
7775            self.write_space();
7776            self.write_keyword("USING TEMPLATE");
7777            self.write_space();
7778            self.generate_expression(using_template)?;
7779            return Ok(());
7780        }
7781
7782        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
7783        if is_clickhouse {
7784            if let Some(ref clone_source) = ct.clone_source {
7785                self.write_space();
7786                self.write_keyword("AS");
7787                self.write_space();
7788                self.generate_table(clone_source)?;
7789            }
7790        }
7791
7792        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7793        if !is_clickhouse {
7794            if let Some(ref clone_source) = ct.clone_source {
7795                self.write_space();
7796                if ct.is_copy && self.config.supports_table_copy {
7797                    // BigQuery uses COPY
7798                    self.write_keyword("COPY");
7799                } else if ct.shallow_clone {
7800                    self.write_keyword("SHALLOW CLONE");
7801                } else if ct.deep_clone {
7802                    self.write_keyword("DEEP CLONE");
7803                } else {
7804                    self.write_keyword("CLONE");
7805                }
7806                self.write_space();
7807                self.generate_table(clone_source)?;
7808                // Generate AT/BEFORE time travel clause (stored as Raw expression)
7809                if let Some(ref at_clause) = ct.clone_at_clause {
7810                    self.write_space();
7811                    self.generate_expression(at_clause)?;
7812                }
7813                return Ok(());
7814            }
7815        }
7816
7817        // Handle PARTITION OF property
7818        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7819        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7820        if let Some(ref partition_of) = ct.partition_of {
7821            self.write_space();
7822
7823            // Extract the PartitionedOfProperty parts to generate them separately
7824            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7825                // Output: PARTITION OF <table>
7826                self.write_keyword("PARTITION OF");
7827                self.write_space();
7828                self.generate_expression(&pop.this)?;
7829
7830                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7831                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7832                    self.write(" (");
7833                    let mut first = true;
7834                    for col in &ct.columns {
7835                        if !first {
7836                            self.write(", ");
7837                        }
7838                        first = false;
7839                        self.generate_column_def(col)?;
7840                    }
7841                    for constraint in &ct.constraints {
7842                        if !first {
7843                            self.write(", ");
7844                        }
7845                        first = false;
7846                        self.generate_table_constraint(constraint)?;
7847                    }
7848                    self.write(")");
7849                }
7850
7851                // Output partition bound spec: FOR VALUES ... or DEFAULT
7852                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7853                    self.write_space();
7854                    self.write_keyword("FOR VALUES");
7855                    self.write_space();
7856                    self.generate_expression(&pop.expression)?;
7857                } else {
7858                    self.write_space();
7859                    self.write_keyword("DEFAULT");
7860                }
7861            } else {
7862                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7863                self.generate_expression(partition_of)?;
7864
7865                // Output columns/constraints if present
7866                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7867                    self.write(" (");
7868                    let mut first = true;
7869                    for col in &ct.columns {
7870                        if !first {
7871                            self.write(", ");
7872                        }
7873                        first = false;
7874                        self.generate_column_def(col)?;
7875                    }
7876                    for constraint in &ct.constraints {
7877                        if !first {
7878                            self.write(", ");
7879                        }
7880                        first = false;
7881                        self.generate_table_constraint(constraint)?;
7882                    }
7883                    self.write(")");
7884                }
7885            }
7886
7887            // Output table properties (e.g., PARTITION BY RANGE(population))
7888            for prop in &ct.properties {
7889                self.write_space();
7890                self.generate_expression(prop)?;
7891            }
7892
7893            return Ok(());
7894        }
7895
7896        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7897        // This matches Python sqlglot's behavior for SQLite dialect
7898        self.sqlite_inline_pk_columns.clear();
7899        if matches!(
7900            self.config.dialect,
7901            Some(crate::dialects::DialectType::SQLite)
7902        ) {
7903            for constraint in &ct.constraints {
7904                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7905                    // Only inline if: single column, no constraint name, and column exists in table
7906                    if columns.len() == 1 && name.is_none() {
7907                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7908                        // Check if this column exists in the table
7909                        if ct
7910                            .columns
7911                            .iter()
7912                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7913                        {
7914                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7915                        }
7916                    }
7917                }
7918            }
7919        }
7920
7921        // Output columns if present (even for CTAS with columns)
7922        if !ct.columns.is_empty() {
7923            if self.config.pretty {
7924                // Pretty print: each column on new line
7925                self.write(" (");
7926                self.write_newline();
7927                self.indent_level += 1;
7928                for (i, col) in ct.columns.iter().enumerate() {
7929                    if i > 0 {
7930                        self.write(",");
7931                        self.write_newline();
7932                    }
7933                    self.write_indent();
7934                    self.generate_column_def(col)?;
7935                }
7936                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7937                for constraint in &ct.constraints {
7938                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7939                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7940                        if columns.len() == 1
7941                            && name.is_none()
7942                            && self
7943                                .sqlite_inline_pk_columns
7944                                .contains(&columns[0].name.to_ascii_lowercase())
7945                        {
7946                            continue;
7947                        }
7948                    }
7949                    self.write(",");
7950                    self.write_newline();
7951                    self.write_indent();
7952                    self.generate_table_constraint(constraint)?;
7953                }
7954                self.indent_level -= 1;
7955                self.write_newline();
7956                self.write(")");
7957            } else {
7958                self.write(" (");
7959                for (i, col) in ct.columns.iter().enumerate() {
7960                    if i > 0 {
7961                        self.write(", ");
7962                    }
7963                    self.generate_column_def(col)?;
7964                }
7965                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7966                let mut first_constraint = true;
7967                for constraint in &ct.constraints {
7968                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7969                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7970                        if columns.len() == 1
7971                            && name.is_none()
7972                            && self
7973                                .sqlite_inline_pk_columns
7974                                .contains(&columns[0].name.to_ascii_lowercase())
7975                        {
7976                            continue;
7977                        }
7978                    }
7979                    if first_constraint {
7980                        self.write(", ");
7981                        first_constraint = false;
7982                    } else {
7983                        self.write(", ");
7984                    }
7985                    self.generate_table_constraint(constraint)?;
7986                }
7987                self.write(")");
7988            }
7989        } else if !ct.constraints.is_empty() {
7990            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7991            let has_like_only = ct
7992                .constraints
7993                .iter()
7994                .all(|c| matches!(c, TableConstraint::Like { .. }));
7995            let has_tags_only = ct
7996                .constraints
7997                .iter()
7998                .all(|c| matches!(c, TableConstraint::Tags(_)));
7999            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8000            // Most dialects: CREATE TABLE A LIKE B (no parens)
8001            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8002            let is_pg_like = matches!(
8003                self.config.dialect,
8004                Some(crate::dialects::DialectType::PostgreSQL)
8005                    | Some(crate::dialects::DialectType::CockroachDB)
8006                    | Some(crate::dialects::DialectType::Materialize)
8007                    | Some(crate::dialects::DialectType::RisingWave)
8008                    | Some(crate::dialects::DialectType::Redshift)
8009                    | Some(crate::dialects::DialectType::Presto)
8010                    | Some(crate::dialects::DialectType::Trino)
8011                    | Some(crate::dialects::DialectType::Athena)
8012            );
8013            let use_parens = if has_like_only {
8014                is_pg_like
8015            } else {
8016                !has_tags_only
8017            };
8018            if self.config.pretty && use_parens {
8019                self.write(" (");
8020                self.write_newline();
8021                self.indent_level += 1;
8022                for (i, constraint) in ct.constraints.iter().enumerate() {
8023                    if i > 0 {
8024                        self.write(",");
8025                        self.write_newline();
8026                    }
8027                    self.write_indent();
8028                    self.generate_table_constraint(constraint)?;
8029                }
8030                self.indent_level -= 1;
8031                self.write_newline();
8032                self.write(")");
8033            } else {
8034                if use_parens {
8035                    self.write(" (");
8036                } else {
8037                    self.write_space();
8038                }
8039                for (i, constraint) in ct.constraints.iter().enumerate() {
8040                    if i > 0 {
8041                        self.write(", ");
8042                    }
8043                    self.generate_table_constraint(constraint)?;
8044                }
8045                if use_parens {
8046                    self.write(")");
8047                }
8048            }
8049        }
8050
8051        // TSQL ON filegroup or ON filegroup (partition_column) clause
8052        if let Some(ref on_prop) = ct.on_property {
8053            self.write(" ");
8054            self.write_keyword("ON");
8055            self.write(" ");
8056            self.generate_expression(&on_prop.this)?;
8057        }
8058
8059        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8060        if !ct.with_partition_columns.is_empty() {
8061            if self.config.pretty {
8062                self.write_newline();
8063            } else {
8064                self.write_space();
8065            }
8066            self.write_keyword("WITH PARTITION COLUMNS");
8067            self.write(" (");
8068            if self.config.pretty {
8069                self.write_newline();
8070                self.indent_level += 1;
8071                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8072                    if i > 0 {
8073                        self.write(",");
8074                        self.write_newline();
8075                    }
8076                    self.write_indent();
8077                    self.generate_column_def(col)?;
8078                }
8079                self.indent_level -= 1;
8080                self.write_newline();
8081            } else {
8082                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8083                    if i > 0 {
8084                        self.write(", ");
8085                    }
8086                    self.generate_column_def(col)?;
8087                }
8088            }
8089            self.write(")");
8090        }
8091
8092        // BigQuery: WITH CONNECTION `project.region.connection`
8093        if let Some(ref conn) = ct.with_connection {
8094            if self.config.pretty {
8095                self.write_newline();
8096            } else {
8097                self.write_space();
8098            }
8099            self.write_keyword("WITH CONNECTION");
8100            self.write_space();
8101            self.generate_table(conn)?;
8102        }
8103
8104        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8105        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8106        if !is_clickhouse {
8107            for prop in &ct.properties {
8108                if let Expression::SchemaCommentProperty(_) = prop {
8109                    if self.config.pretty {
8110                        self.write_newline();
8111                    } else {
8112                        self.write_space();
8113                    }
8114                    self.generate_expression(prop)?;
8115                }
8116            }
8117        }
8118
8119        // WITH properties (output after columns if columns exist, otherwise before AS)
8120        if !ct.with_properties.is_empty() {
8121            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8122            let is_snowflake_special_table = matches!(
8123                self.config.dialect,
8124                Some(crate::dialects::DialectType::Snowflake)
8125            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8126                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8127            if is_snowflake_special_table {
8128                for (key, value) in &ct.with_properties {
8129                    self.write_space();
8130                    self.write(key);
8131                    self.write("=");
8132                    self.write(value);
8133                }
8134            } else if self.config.pretty {
8135                self.write_newline();
8136                self.write_keyword("WITH");
8137                self.write(" (");
8138                self.write_newline();
8139                self.indent_level += 1;
8140                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8141                    if i > 0 {
8142                        self.write(",");
8143                        self.write_newline();
8144                    }
8145                    self.write_indent();
8146                    self.write(key);
8147                    self.write("=");
8148                    self.write(value);
8149                }
8150                self.indent_level -= 1;
8151                self.write_newline();
8152                self.write(")");
8153            } else {
8154                self.write_space();
8155                self.write_keyword("WITH");
8156                self.write(" (");
8157                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8158                    if i > 0 {
8159                        self.write(", ");
8160                    }
8161                    self.write(key);
8162                    self.write("=");
8163                    self.write(value);
8164                }
8165                self.write(")");
8166            }
8167        }
8168
8169        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8170            if is_clickhouse && ct.as_select.is_some() {
8171                let mut pre = Vec::new();
8172                let mut post = Vec::new();
8173                for prop in &ct.properties {
8174                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8175                        post.push(prop);
8176                    } else {
8177                        pre.push(prop);
8178                    }
8179                }
8180                (pre, post)
8181            } else {
8182                (ct.properties.iter().collect(), Vec::new())
8183            };
8184
8185        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8186        for prop in pre_as_properties {
8187            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8188            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8189                continue;
8190            }
8191            if self.config.pretty {
8192                self.write_newline();
8193            } else {
8194                self.write_space();
8195            }
8196            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8197            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8198            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8199            if let Expression::Properties(props) = prop {
8200                let is_hive_dialect = matches!(
8201                    self.config.dialect,
8202                    Some(crate::dialects::DialectType::Hive)
8203                        | Some(crate::dialects::DialectType::Spark)
8204                        | Some(crate::dialects::DialectType::Databricks)
8205                        | Some(crate::dialects::DialectType::Athena)
8206                );
8207                let is_doris_starrocks = matches!(
8208                    self.config.dialect,
8209                    Some(crate::dialects::DialectType::Doris)
8210                        | Some(crate::dialects::DialectType::StarRocks)
8211                );
8212                if is_hive_dialect {
8213                    self.generate_tblproperties_clause(&props.expressions)?;
8214                } else if is_doris_starrocks {
8215                    self.generate_properties_clause(&props.expressions)?;
8216                } else {
8217                    self.generate_options_clause(&props.expressions)?;
8218                }
8219            } else {
8220                self.generate_expression(prop)?;
8221            }
8222        }
8223
8224        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8225        for prop in &ct.post_table_properties {
8226            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8227                self.write(" WITH(");
8228                self.generate_system_versioning_content(svp)?;
8229                self.write(")");
8230            } else if let Expression::Properties(props) = prop {
8231                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8232                let is_doris_starrocks = matches!(
8233                    self.config.dialect,
8234                    Some(crate::dialects::DialectType::Doris)
8235                        | Some(crate::dialects::DialectType::StarRocks)
8236                );
8237                self.write_space();
8238                if is_doris_starrocks {
8239                    self.generate_properties_clause(&props.expressions)?;
8240                } else {
8241                    self.generate_options_clause(&props.expressions)?;
8242                }
8243            } else {
8244                self.write_space();
8245                self.generate_expression(prop)?;
8246            }
8247        }
8248
8249        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8250        // Only output for StarRocks target
8251        if let Some(ref rollup) = ct.rollup {
8252            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8253                self.write_space();
8254                self.generate_rollup_property(rollup)?;
8255            }
8256        }
8257
8258        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8259        // Only output for MySQL-compatible dialects; strip for others during transpilation
8260        // COMMENT is also used by Hive/Spark so we selectively preserve it
8261        let is_mysql_compatible = matches!(
8262            self.config.dialect,
8263            Some(DialectType::MySQL)
8264                | Some(DialectType::SingleStore)
8265                | Some(DialectType::Doris)
8266                | Some(DialectType::StarRocks)
8267                | None
8268        );
8269        let is_hive_compatible = matches!(
8270            self.config.dialect,
8271            Some(DialectType::Hive)
8272                | Some(DialectType::Spark)
8273                | Some(DialectType::Databricks)
8274                | Some(DialectType::Athena)
8275        );
8276        let mysql_pretty_options =
8277            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8278        for (key, value) in &ct.mysql_table_options {
8279            // Skip non-MySQL-specific options for non-MySQL targets
8280            let should_output = if is_mysql_compatible {
8281                true
8282            } else if is_hive_compatible && key == "COMMENT" {
8283                true // COMMENT is valid in Hive/Spark table definitions
8284            } else {
8285                false
8286            };
8287            if should_output {
8288                if mysql_pretty_options {
8289                    self.write_newline();
8290                    self.write_indent();
8291                } else {
8292                    self.write_space();
8293                }
8294                self.write_keyword(key);
8295                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8296                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8297                    self.write_space();
8298                } else {
8299                    self.write("=");
8300                }
8301                self.write(value);
8302            }
8303        }
8304
8305        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8306        if ct.temporary
8307            && matches!(
8308                self.config.dialect,
8309                Some(DialectType::Spark) | Some(DialectType::Databricks)
8310            )
8311            && ct.as_select.is_none()
8312        {
8313            self.write_space();
8314            self.write_keyword("USING PARQUET");
8315        }
8316
8317        // PostgreSQL INHERITS clause
8318        if !ct.inherits.is_empty() {
8319            self.write_space();
8320            self.write_keyword("INHERITS");
8321            self.write(" (");
8322            for (i, parent) in ct.inherits.iter().enumerate() {
8323                if i > 0 {
8324                    self.write(", ");
8325                }
8326                self.generate_table(parent)?;
8327            }
8328            self.write(")");
8329        }
8330
8331        // CREATE TABLE AS SELECT
8332        if let Some(ref query) = ct.as_select {
8333            self.write_space();
8334            self.write_keyword("AS");
8335            self.write_space();
8336            let source_is_clickhouse =
8337                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8338            let wrap_as_select =
8339                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8340            if wrap_as_select {
8341                self.write("(");
8342            }
8343            self.generate_expression(query)?;
8344            if wrap_as_select {
8345                self.write(")");
8346            }
8347
8348            // Teradata: WITH DATA / WITH NO DATA
8349            if let Some(with_data) = ct.with_data {
8350                self.write_space();
8351                self.write_keyword("WITH");
8352                if !with_data {
8353                    self.write_space();
8354                    self.write_keyword("NO");
8355                }
8356                self.write_space();
8357                self.write_keyword("DATA");
8358            }
8359
8360            // Teradata: AND STATISTICS / AND NO STATISTICS
8361            if let Some(with_statistics) = ct.with_statistics {
8362                self.write_space();
8363                self.write_keyword("AND");
8364                if !with_statistics {
8365                    self.write_space();
8366                    self.write_keyword("NO");
8367                }
8368                self.write_space();
8369                self.write_keyword("STATISTICS");
8370            }
8371
8372            // Teradata: Index specifications
8373            for index in &ct.teradata_indexes {
8374                self.write_space();
8375                match index.kind {
8376                    TeradataIndexKind::NoPrimary => {
8377                        self.write_keyword("NO PRIMARY INDEX");
8378                    }
8379                    TeradataIndexKind::Primary => {
8380                        self.write_keyword("PRIMARY INDEX");
8381                    }
8382                    TeradataIndexKind::PrimaryAmp => {
8383                        self.write_keyword("PRIMARY AMP INDEX");
8384                    }
8385                    TeradataIndexKind::Unique => {
8386                        self.write_keyword("UNIQUE INDEX");
8387                    }
8388                    TeradataIndexKind::UniquePrimary => {
8389                        self.write_keyword("UNIQUE PRIMARY INDEX");
8390                    }
8391                    TeradataIndexKind::Secondary => {
8392                        self.write_keyword("INDEX");
8393                    }
8394                }
8395                // Output index name if present
8396                if let Some(ref name) = index.name {
8397                    self.write_space();
8398                    self.write(name);
8399                }
8400                // Output columns if present
8401                if !index.columns.is_empty() {
8402                    self.write(" (");
8403                    for (i, col) in index.columns.iter().enumerate() {
8404                        if i > 0 {
8405                            self.write(", ");
8406                        }
8407                        self.write(col);
8408                    }
8409                    self.write(")");
8410                }
8411            }
8412
8413            // Teradata: ON COMMIT behavior for volatile tables
8414            if let Some(ref on_commit) = ct.on_commit {
8415                self.write_space();
8416                self.write_keyword("ON COMMIT");
8417                self.write_space();
8418                match on_commit {
8419                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8420                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8421                }
8422            }
8423
8424            if !post_as_properties.is_empty() {
8425                for prop in post_as_properties {
8426                    self.write_space();
8427                    self.generate_expression(prop)?;
8428                }
8429            }
8430
8431            // Restore Athena Hive context before early return
8432            self.athena_hive_context = saved_athena_hive_context;
8433            return Ok(());
8434        }
8435
8436        // ON COMMIT behavior (for non-CTAS tables)
8437        if let Some(ref on_commit) = ct.on_commit {
8438            self.write_space();
8439            self.write_keyword("ON COMMIT");
8440            self.write_space();
8441            match on_commit {
8442                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8443                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8444            }
8445        }
8446
8447        // Restore Athena Hive context
8448        self.athena_hive_context = saved_athena_hive_context;
8449
8450        Ok(())
8451    }
8452
8453    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8454    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8455    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8456        // Output column name
8457        self.generate_identifier(&col.name)?;
8458        // Output data type if known
8459        if !matches!(col.data_type, DataType::Unknown) {
8460            self.write_space();
8461            self.generate_data_type(&col.data_type)?;
8462        }
8463        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8464        for constraint in &col.constraints {
8465            if let ColumnConstraint::Path(path_expr) = constraint {
8466                self.write_space();
8467                self.write_keyword("PATH");
8468                self.write_space();
8469                self.generate_expression(path_expr)?;
8470            }
8471        }
8472        Ok(())
8473    }
8474
8475    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8476        // Check if this is a TSQL computed column (no data type)
8477        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8478            && col
8479                .constraints
8480                .iter()
8481                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8482        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8483        let omit_computed_type = !self.config.computed_column_with_type
8484            && col
8485                .constraints
8486                .iter()
8487                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8488
8489        // Check if this is a partition column spec (no data type, type is Unknown)
8490        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8491        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8492
8493        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8494        // Also check the no_type flag for SQLite columns without types
8495        let has_no_type = col.no_type
8496            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8497                && col.constraints.is_empty());
8498
8499        self.generate_identifier(&col.name)?;
8500
8501        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8502        let serial_expansion = if matches!(
8503            self.config.dialect,
8504            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8505        ) {
8506            if let DataType::Custom { ref name } = col.data_type {
8507                if name.eq_ignore_ascii_case("SERIAL") {
8508                    Some("INT")
8509                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8510                    Some("BIGINT")
8511                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8512                    Some("SMALLINT")
8513                } else {
8514                    None
8515                }
8516            } else {
8517                None
8518            }
8519        } else {
8520            None
8521        };
8522
8523        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8524        {
8525            self.write_space();
8526            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8527            // since ClickHouse uses explicit Nullable() in its type system.
8528            let saved_nullable_depth = self.clickhouse_nullable_depth;
8529            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8530                self.clickhouse_nullable_depth = -1;
8531            }
8532            if let Some(int_type) = serial_expansion {
8533                // SERIAL -> INT (+ constraints added below)
8534                self.write_keyword(int_type);
8535            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8536                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8537                let unsigned_type = match &col.data_type {
8538                    DataType::Int { .. } => Some("UINTEGER"),
8539                    DataType::BigInt { .. } => Some("UBIGINT"),
8540                    DataType::SmallInt { .. } => Some("USMALLINT"),
8541                    DataType::TinyInt { .. } => Some("UTINYINT"),
8542                    _ => None,
8543                };
8544                if let Some(utype) = unsigned_type {
8545                    self.write_keyword(utype);
8546                } else {
8547                    self.generate_data_type(&col.data_type)?;
8548                }
8549            } else {
8550                self.generate_data_type(&col.data_type)?;
8551            }
8552            self.clickhouse_nullable_depth = saved_nullable_depth;
8553        }
8554
8555        // MySQL type modifiers (must come right after data type)
8556        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8557        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8558            self.write_space();
8559            self.write_keyword("UNSIGNED");
8560        }
8561        if col.zerofill {
8562            self.write_space();
8563            self.write_keyword("ZEROFILL");
8564        }
8565
8566        // Teradata column attributes (must come right after data type, in specific order)
8567        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8568
8569        if let Some(ref charset) = col.character_set {
8570            self.write_space();
8571            self.write_keyword("CHARACTER SET");
8572            self.write_space();
8573            self.write(charset);
8574        }
8575
8576        if col.uppercase {
8577            self.write_space();
8578            self.write_keyword("UPPERCASE");
8579        }
8580
8581        if let Some(casespecific) = col.casespecific {
8582            self.write_space();
8583            if casespecific {
8584                self.write_keyword("CASESPECIFIC");
8585            } else {
8586                self.write_keyword("NOT CASESPECIFIC");
8587            }
8588        }
8589
8590        if let Some(ref format) = col.format {
8591            self.write_space();
8592            self.write_keyword("FORMAT");
8593            self.write(" '");
8594            self.write(format);
8595            self.write("'");
8596        }
8597
8598        if let Some(ref title) = col.title {
8599            self.write_space();
8600            self.write_keyword("TITLE");
8601            self.write(" '");
8602            self.write(title);
8603            self.write("'");
8604        }
8605
8606        if let Some(length) = col.inline_length {
8607            self.write_space();
8608            self.write_keyword("INLINE LENGTH");
8609            self.write(" ");
8610            self.write(&length.to_string());
8611        }
8612
8613        if let Some(ref compress) = col.compress {
8614            self.write_space();
8615            self.write_keyword("COMPRESS");
8616            if !compress.is_empty() {
8617                // Single string literal: output without parentheses (Teradata syntax)
8618                if compress.len() == 1 {
8619                    if let Expression::Literal(lit) = &compress[0] {
8620                        if let Literal::String(_) = lit.as_ref() {
8621                            self.write_space();
8622                            self.generate_expression(&compress[0])?;
8623                        }
8624                    } else {
8625                        self.write(" (");
8626                        self.generate_expression(&compress[0])?;
8627                        self.write(")");
8628                    }
8629                } else {
8630                    self.write(" (");
8631                    for (i, val) in compress.iter().enumerate() {
8632                        if i > 0 {
8633                            self.write(", ");
8634                        }
8635                        self.generate_expression(val)?;
8636                    }
8637                    self.write(")");
8638                }
8639            }
8640        }
8641
8642        // Column constraints - output in original order if constraint_order is populated
8643        // Otherwise fall back to legacy fixed order for backward compatibility
8644        if !col.constraint_order.is_empty() {
8645            // Use constraint_order for original ordering
8646            // Track indices for constraints stored in the constraints Vec
8647            let mut references_idx = 0;
8648            let mut check_idx = 0;
8649            let mut generated_idx = 0;
8650            let mut collate_idx = 0;
8651            let mut comment_idx = 0;
8652            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8653            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8654            let defer_not_null_after_identity = false;
8655            let mut pending_not_null_after_identity = false;
8656
8657            for constraint_type in &col.constraint_order {
8658                match constraint_type {
8659                    ConstraintType::PrimaryKey => {
8660                        // Materialize doesn't support PRIMARY KEY column constraints
8661                        if col.primary_key
8662                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8663                        {
8664                            if let Some(ref cname) = col.primary_key_constraint_name {
8665                                self.write_space();
8666                                self.write_keyword("CONSTRAINT");
8667                                self.write_space();
8668                                self.write(cname);
8669                            }
8670                            self.write_space();
8671                            self.write_keyword("PRIMARY KEY");
8672                            if let Some(ref order) = col.primary_key_order {
8673                                self.write_space();
8674                                match order {
8675                                    SortOrder::Asc => self.write_keyword("ASC"),
8676                                    SortOrder::Desc => self.write_keyword("DESC"),
8677                                }
8678                            }
8679                        }
8680                    }
8681                    ConstraintType::Unique => {
8682                        if col.unique {
8683                            if let Some(ref cname) = col.unique_constraint_name {
8684                                self.write_space();
8685                                self.write_keyword("CONSTRAINT");
8686                                self.write_space();
8687                                self.write(cname);
8688                            }
8689                            self.write_space();
8690                            self.write_keyword("UNIQUE");
8691                            // PostgreSQL 15+: NULLS NOT DISTINCT
8692                            if col.unique_nulls_not_distinct {
8693                                self.write(" NULLS NOT DISTINCT");
8694                            }
8695                        }
8696                    }
8697                    ConstraintType::NotNull => {
8698                        if col.nullable == Some(false) {
8699                            if defer_not_null_after_identity {
8700                                pending_not_null_after_identity = true;
8701                                continue;
8702                            }
8703                            if let Some(ref cname) = col.not_null_constraint_name {
8704                                self.write_space();
8705                                self.write_keyword("CONSTRAINT");
8706                                self.write_space();
8707                                self.write(cname);
8708                            }
8709                            self.write_space();
8710                            self.write_keyword("NOT NULL");
8711                        }
8712                    }
8713                    ConstraintType::Null => {
8714                        if col.nullable == Some(true) {
8715                            self.write_space();
8716                            self.write_keyword("NULL");
8717                        }
8718                    }
8719                    ConstraintType::Default => {
8720                        if let Some(ref default) = col.default {
8721                            self.write_space();
8722                            self.write_keyword("DEFAULT");
8723                            self.write_space();
8724                            self.generate_expression(default)?;
8725                        }
8726                    }
8727                    ConstraintType::AutoIncrement => {
8728                        if col.auto_increment {
8729                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8730                            if matches!(
8731                                self.config.dialect,
8732                                Some(crate::dialects::DialectType::DuckDB)
8733                            ) {
8734                                // Skip - DuckDB uses sequences or rowid instead
8735                            } else if matches!(
8736                                self.config.dialect,
8737                                Some(crate::dialects::DialectType::Materialize)
8738                            ) {
8739                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8740                                if !matches!(col.nullable, Some(false)) {
8741                                    self.write_space();
8742                                    self.write_keyword("NOT NULL");
8743                                }
8744                            } else if matches!(
8745                                self.config.dialect,
8746                                Some(crate::dialects::DialectType::PostgreSQL)
8747                            ) {
8748                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8749                                self.write_space();
8750                                self.generate_auto_increment_keyword(col)?;
8751                            } else {
8752                                self.write_space();
8753                                self.generate_auto_increment_keyword(col)?;
8754                                if pending_not_null_after_identity {
8755                                    self.write_space();
8756                                    self.write_keyword("NOT NULL");
8757                                    pending_not_null_after_identity = false;
8758                                }
8759                            }
8760                        } // close else for DuckDB skip
8761                    }
8762                    ConstraintType::References => {
8763                        // Find next References constraint
8764                        while references_idx < col.constraints.len() {
8765                            if let ColumnConstraint::References(fk_ref) =
8766                                &col.constraints[references_idx]
8767                            {
8768                                // CONSTRAINT name if present
8769                                if let Some(ref name) = fk_ref.constraint_name {
8770                                    self.write_space();
8771                                    self.write_keyword("CONSTRAINT");
8772                                    self.write_space();
8773                                    self.write(name);
8774                                }
8775                                self.write_space();
8776                                if fk_ref.has_foreign_key_keywords {
8777                                    self.write_keyword("FOREIGN KEY");
8778                                    self.write_space();
8779                                }
8780                                self.write_keyword("REFERENCES");
8781                                self.write_space();
8782                                self.generate_table(&fk_ref.table)?;
8783                                if !fk_ref.columns.is_empty() {
8784                                    self.write(" (");
8785                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8786                                        if i > 0 {
8787                                            self.write(", ");
8788                                        }
8789                                        self.generate_identifier(c)?;
8790                                    }
8791                                    self.write(")");
8792                                }
8793                                self.generate_referential_actions(fk_ref)?;
8794                                references_idx += 1;
8795                                break;
8796                            }
8797                            references_idx += 1;
8798                        }
8799                    }
8800                    ConstraintType::Check => {
8801                        // Find next Check constraint
8802                        while check_idx < col.constraints.len() {
8803                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8804                                // Output CONSTRAINT name if present (only for first CHECK)
8805                                if check_idx == 0 {
8806                                    if let Some(ref cname) = col.check_constraint_name {
8807                                        self.write_space();
8808                                        self.write_keyword("CONSTRAINT");
8809                                        self.write_space();
8810                                        self.write(cname);
8811                                    }
8812                                }
8813                                self.write_space();
8814                                self.write_keyword("CHECK");
8815                                self.write(" (");
8816                                self.generate_expression(expr)?;
8817                                self.write(")");
8818                                check_idx += 1;
8819                                break;
8820                            }
8821                            check_idx += 1;
8822                        }
8823                    }
8824                    ConstraintType::GeneratedAsIdentity => {
8825                        // Find next GeneratedAsIdentity constraint
8826                        while generated_idx < col.constraints.len() {
8827                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8828                                &col.constraints[generated_idx]
8829                            {
8830                                self.write_space();
8831                                // Redshift uses IDENTITY(start, increment) syntax
8832                                if matches!(
8833                                    self.config.dialect,
8834                                    Some(crate::dialects::DialectType::Redshift)
8835                                ) {
8836                                    self.write_keyword("IDENTITY");
8837                                    self.write("(");
8838                                    if let Some(ref start) = gen.start {
8839                                        self.generate_expression(start)?;
8840                                    } else {
8841                                        self.write("0");
8842                                    }
8843                                    self.write(", ");
8844                                    if let Some(ref incr) = gen.increment {
8845                                        self.generate_expression(incr)?;
8846                                    } else {
8847                                        self.write("1");
8848                                    }
8849                                    self.write(")");
8850                                } else {
8851                                    self.write_keyword("GENERATED");
8852                                    if gen.always {
8853                                        self.write_space();
8854                                        self.write_keyword("ALWAYS");
8855                                    } else {
8856                                        self.write_space();
8857                                        self.write_keyword("BY DEFAULT");
8858                                        if gen.on_null {
8859                                            self.write_space();
8860                                            self.write_keyword("ON NULL");
8861                                        }
8862                                    }
8863                                    self.write_space();
8864                                    self.write_keyword("AS IDENTITY");
8865
8866                                    let has_options = gen.start.is_some()
8867                                        || gen.increment.is_some()
8868                                        || gen.minvalue.is_some()
8869                                        || gen.maxvalue.is_some()
8870                                        || gen.cycle.is_some();
8871                                    if has_options {
8872                                        self.write(" (");
8873                                        let mut first = true;
8874                                        if let Some(ref start) = gen.start {
8875                                            if !first {
8876                                                self.write(" ");
8877                                            }
8878                                            first = false;
8879                                            self.write_keyword("START WITH");
8880                                            self.write_space();
8881                                            self.generate_expression(start)?;
8882                                        }
8883                                        if let Some(ref incr) = gen.increment {
8884                                            if !first {
8885                                                self.write(" ");
8886                                            }
8887                                            first = false;
8888                                            self.write_keyword("INCREMENT BY");
8889                                            self.write_space();
8890                                            self.generate_expression(incr)?;
8891                                        }
8892                                        if let Some(ref minv) = gen.minvalue {
8893                                            if !first {
8894                                                self.write(" ");
8895                                            }
8896                                            first = false;
8897                                            self.write_keyword("MINVALUE");
8898                                            self.write_space();
8899                                            self.generate_expression(minv)?;
8900                                        }
8901                                        if let Some(ref maxv) = gen.maxvalue {
8902                                            if !first {
8903                                                self.write(" ");
8904                                            }
8905                                            first = false;
8906                                            self.write_keyword("MAXVALUE");
8907                                            self.write_space();
8908                                            self.generate_expression(maxv)?;
8909                                        }
8910                                        if let Some(cycle) = gen.cycle {
8911                                            if !first {
8912                                                self.write(" ");
8913                                            }
8914                                            if cycle {
8915                                                self.write_keyword("CYCLE");
8916                                            } else {
8917                                                self.write_keyword("NO CYCLE");
8918                                            }
8919                                        }
8920                                        self.write(")");
8921                                    }
8922                                }
8923                                generated_idx += 1;
8924                                break;
8925                            }
8926                            generated_idx += 1;
8927                        }
8928                    }
8929                    ConstraintType::Collate => {
8930                        // Find next Collate constraint
8931                        while collate_idx < col.constraints.len() {
8932                            if let ColumnConstraint::Collate(collation) =
8933                                &col.constraints[collate_idx]
8934                            {
8935                                self.write_space();
8936                                self.write_keyword("COLLATE");
8937                                self.write_space();
8938                                self.generate_identifier(collation)?;
8939                                collate_idx += 1;
8940                                break;
8941                            }
8942                            collate_idx += 1;
8943                        }
8944                    }
8945                    ConstraintType::Comment => {
8946                        // Find next Comment constraint
8947                        while comment_idx < col.constraints.len() {
8948                            if let ColumnConstraint::Comment(comment) =
8949                                &col.constraints[comment_idx]
8950                            {
8951                                self.write_space();
8952                                self.write_keyword("COMMENT");
8953                                self.write_space();
8954                                self.generate_string_literal(comment)?;
8955                                comment_idx += 1;
8956                                break;
8957                            }
8958                            comment_idx += 1;
8959                        }
8960                    }
8961                    ConstraintType::Tags => {
8962                        // Find next Tags constraint (Snowflake)
8963                        for constraint in &col.constraints {
8964                            if let ColumnConstraint::Tags(tags) = constraint {
8965                                self.write_space();
8966                                self.write_keyword("TAG");
8967                                self.write(" (");
8968                                for (i, expr) in tags.expressions.iter().enumerate() {
8969                                    if i > 0 {
8970                                        self.write(", ");
8971                                    }
8972                                    self.generate_expression(expr)?;
8973                                }
8974                                self.write(")");
8975                                break;
8976                            }
8977                        }
8978                    }
8979                    ConstraintType::ComputedColumn => {
8980                        // Find next ComputedColumn constraint
8981                        for constraint in &col.constraints {
8982                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8983                                self.write_space();
8984                                self.generate_computed_column_inline(cc)?;
8985                                break;
8986                            }
8987                        }
8988                    }
8989                    ConstraintType::GeneratedAsRow => {
8990                        // Find next GeneratedAsRow constraint
8991                        for constraint in &col.constraints {
8992                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8993                                self.write_space();
8994                                self.generate_generated_as_row_inline(gar)?;
8995                                break;
8996                            }
8997                        }
8998                    }
8999                    ConstraintType::OnUpdate => {
9000                        if let Some(ref expr) = col.on_update {
9001                            self.write_space();
9002                            self.write_keyword("ON UPDATE");
9003                            self.write_space();
9004                            self.generate_expression(expr)?;
9005                        }
9006                    }
9007                    ConstraintType::Encode => {
9008                        if let Some(ref encoding) = col.encoding {
9009                            self.write_space();
9010                            self.write_keyword("ENCODE");
9011                            self.write_space();
9012                            self.write(encoding);
9013                        }
9014                    }
9015                    ConstraintType::Path => {
9016                        // Find next Path constraint
9017                        for constraint in &col.constraints {
9018                            if let ColumnConstraint::Path(path_expr) = constraint {
9019                                self.write_space();
9020                                self.write_keyword("PATH");
9021                                self.write_space();
9022                                self.generate_expression(path_expr)?;
9023                                break;
9024                            }
9025                        }
9026                    }
9027                }
9028            }
9029            if pending_not_null_after_identity {
9030                self.write_space();
9031                self.write_keyword("NOT NULL");
9032            }
9033        } else {
9034            // Legacy fixed order for backward compatibility
9035            if col.primary_key {
9036                self.write_space();
9037                self.write_keyword("PRIMARY KEY");
9038                if let Some(ref order) = col.primary_key_order {
9039                    self.write_space();
9040                    match order {
9041                        SortOrder::Asc => self.write_keyword("ASC"),
9042                        SortOrder::Desc => self.write_keyword("DESC"),
9043                    }
9044                }
9045            }
9046
9047            if col.unique {
9048                self.write_space();
9049                self.write_keyword("UNIQUE");
9050                // PostgreSQL 15+: NULLS NOT DISTINCT
9051                if col.unique_nulls_not_distinct {
9052                    self.write(" NULLS NOT DISTINCT");
9053                }
9054            }
9055
9056            match col.nullable {
9057                Some(false) => {
9058                    self.write_space();
9059                    self.write_keyword("NOT NULL");
9060                }
9061                Some(true) => {
9062                    self.write_space();
9063                    self.write_keyword("NULL");
9064                }
9065                None => {}
9066            }
9067
9068            if let Some(ref default) = col.default {
9069                self.write_space();
9070                self.write_keyword("DEFAULT");
9071                self.write_space();
9072                self.generate_expression(default)?;
9073            }
9074
9075            if col.auto_increment {
9076                self.write_space();
9077                self.generate_auto_increment_keyword(col)?;
9078            }
9079
9080            // Column-level constraints from Vec
9081            for constraint in &col.constraints {
9082                match constraint {
9083                    ColumnConstraint::References(fk_ref) => {
9084                        self.write_space();
9085                        if fk_ref.has_foreign_key_keywords {
9086                            self.write_keyword("FOREIGN KEY");
9087                            self.write_space();
9088                        }
9089                        self.write_keyword("REFERENCES");
9090                        self.write_space();
9091                        self.generate_table(&fk_ref.table)?;
9092                        if !fk_ref.columns.is_empty() {
9093                            self.write(" (");
9094                            for (i, c) in fk_ref.columns.iter().enumerate() {
9095                                if i > 0 {
9096                                    self.write(", ");
9097                                }
9098                                self.generate_identifier(c)?;
9099                            }
9100                            self.write(")");
9101                        }
9102                        self.generate_referential_actions(fk_ref)?;
9103                    }
9104                    ColumnConstraint::Check(expr) => {
9105                        self.write_space();
9106                        self.write_keyword("CHECK");
9107                        self.write(" (");
9108                        self.generate_expression(expr)?;
9109                        self.write(")");
9110                    }
9111                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9112                        self.write_space();
9113                        // Redshift uses IDENTITY(start, increment) syntax
9114                        if matches!(
9115                            self.config.dialect,
9116                            Some(crate::dialects::DialectType::Redshift)
9117                        ) {
9118                            self.write_keyword("IDENTITY");
9119                            self.write("(");
9120                            if let Some(ref start) = gen.start {
9121                                self.generate_expression(start)?;
9122                            } else {
9123                                self.write("0");
9124                            }
9125                            self.write(", ");
9126                            if let Some(ref incr) = gen.increment {
9127                                self.generate_expression(incr)?;
9128                            } else {
9129                                self.write("1");
9130                            }
9131                            self.write(")");
9132                        } else {
9133                            self.write_keyword("GENERATED");
9134                            if gen.always {
9135                                self.write_space();
9136                                self.write_keyword("ALWAYS");
9137                            } else {
9138                                self.write_space();
9139                                self.write_keyword("BY DEFAULT");
9140                                if gen.on_null {
9141                                    self.write_space();
9142                                    self.write_keyword("ON NULL");
9143                                }
9144                            }
9145                            self.write_space();
9146                            self.write_keyword("AS IDENTITY");
9147
9148                            let has_options = gen.start.is_some()
9149                                || gen.increment.is_some()
9150                                || gen.minvalue.is_some()
9151                                || gen.maxvalue.is_some()
9152                                || gen.cycle.is_some();
9153                            if has_options {
9154                                self.write(" (");
9155                                let mut first = true;
9156                                if let Some(ref start) = gen.start {
9157                                    if !first {
9158                                        self.write(" ");
9159                                    }
9160                                    first = false;
9161                                    self.write_keyword("START WITH");
9162                                    self.write_space();
9163                                    self.generate_expression(start)?;
9164                                }
9165                                if let Some(ref incr) = gen.increment {
9166                                    if !first {
9167                                        self.write(" ");
9168                                    }
9169                                    first = false;
9170                                    self.write_keyword("INCREMENT BY");
9171                                    self.write_space();
9172                                    self.generate_expression(incr)?;
9173                                }
9174                                if let Some(ref minv) = gen.minvalue {
9175                                    if !first {
9176                                        self.write(" ");
9177                                    }
9178                                    first = false;
9179                                    self.write_keyword("MINVALUE");
9180                                    self.write_space();
9181                                    self.generate_expression(minv)?;
9182                                }
9183                                if let Some(ref maxv) = gen.maxvalue {
9184                                    if !first {
9185                                        self.write(" ");
9186                                    }
9187                                    first = false;
9188                                    self.write_keyword("MAXVALUE");
9189                                    self.write_space();
9190                                    self.generate_expression(maxv)?;
9191                                }
9192                                if let Some(cycle) = gen.cycle {
9193                                    if !first {
9194                                        self.write(" ");
9195                                    }
9196                                    if cycle {
9197                                        self.write_keyword("CYCLE");
9198                                    } else {
9199                                        self.write_keyword("NO CYCLE");
9200                                    }
9201                                }
9202                                self.write(")");
9203                            }
9204                        }
9205                    }
9206                    ColumnConstraint::Collate(collation) => {
9207                        self.write_space();
9208                        self.write_keyword("COLLATE");
9209                        self.write_space();
9210                        self.generate_identifier(collation)?;
9211                    }
9212                    ColumnConstraint::Comment(comment) => {
9213                        self.write_space();
9214                        self.write_keyword("COMMENT");
9215                        self.write_space();
9216                        self.generate_string_literal(comment)?;
9217                    }
9218                    ColumnConstraint::Path(path_expr) => {
9219                        self.write_space();
9220                        self.write_keyword("PATH");
9221                        self.write_space();
9222                        self.generate_expression(path_expr)?;
9223                    }
9224                    _ => {} // Other constraints handled above
9225                }
9226            }
9227
9228            // Redshift: ENCODE encoding_type (legacy path)
9229            if let Some(ref encoding) = col.encoding {
9230                self.write_space();
9231                self.write_keyword("ENCODE");
9232                self.write_space();
9233                self.write(encoding);
9234            }
9235        }
9236
9237        // ClickHouse: CODEC(...)
9238        if let Some(ref codec) = col.codec {
9239            self.write_space();
9240            self.write_keyword("CODEC");
9241            self.write("(");
9242            self.write(codec);
9243            self.write(")");
9244        }
9245
9246        if let Some(visible) = col.visible {
9247            self.write_space();
9248            if visible {
9249                self.write_keyword("VISIBLE");
9250            } else {
9251                self.write_keyword("INVISIBLE");
9252            }
9253        }
9254
9255        // ClickHouse: EPHEMERAL [expr]
9256        if let Some(ref ephemeral) = col.ephemeral {
9257            self.write_space();
9258            self.write_keyword("EPHEMERAL");
9259            if let Some(ref expr) = ephemeral {
9260                self.write_space();
9261                self.generate_expression(expr)?;
9262            }
9263        }
9264
9265        // ClickHouse: MATERIALIZED expr
9266        if let Some(ref mat_expr) = col.materialized_expr {
9267            self.write_space();
9268            self.write_keyword("MATERIALIZED");
9269            self.write_space();
9270            self.generate_expression(mat_expr)?;
9271        }
9272
9273        // ClickHouse: ALIAS expr
9274        if let Some(ref alias_expr) = col.alias_expr {
9275            self.write_space();
9276            self.write_keyword("ALIAS");
9277            self.write_space();
9278            self.generate_expression(alias_expr)?;
9279        }
9280
9281        // ClickHouse: TTL expr
9282        if let Some(ref ttl_expr) = col.ttl_expr {
9283            self.write_space();
9284            self.write_keyword("TTL");
9285            self.write_space();
9286            self.generate_expression(ttl_expr)?;
9287        }
9288
9289        // TSQL: NOT FOR REPLICATION
9290        if col.not_for_replication
9291            && matches!(
9292                self.config.dialect,
9293                Some(crate::dialects::DialectType::TSQL)
9294                    | Some(crate::dialects::DialectType::Fabric)
9295            )
9296        {
9297            self.write_space();
9298            self.write_keyword("NOT FOR REPLICATION");
9299        }
9300
9301        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9302        if !col.options.is_empty() {
9303            self.write_space();
9304            self.generate_options_clause(&col.options)?;
9305        }
9306
9307        // SQLite: Inline PRIMARY KEY from table constraint
9308        // This comes at the end, after all existing column constraints
9309        if !col.primary_key
9310            && self
9311                .sqlite_inline_pk_columns
9312                .contains(&col.name.name.to_ascii_lowercase())
9313        {
9314            self.write_space();
9315            self.write_keyword("PRIMARY KEY");
9316        }
9317
9318        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9319        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9320        if serial_expansion.is_some() {
9321            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9322                self.write_space();
9323                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9324            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9325                self.write_space();
9326                self.write_keyword("NOT NULL");
9327            }
9328        }
9329
9330        Ok(())
9331    }
9332
9333    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9334        match constraint {
9335            TableConstraint::PrimaryKey {
9336                name,
9337                columns,
9338                include_columns,
9339                modifiers,
9340                has_constraint_keyword,
9341            } => {
9342                if let Some(ref n) = name {
9343                    if *has_constraint_keyword {
9344                        self.write_keyword("CONSTRAINT");
9345                        self.write_space();
9346                        self.generate_identifier(n)?;
9347                        self.write_space();
9348                    }
9349                }
9350                self.write_keyword("PRIMARY KEY");
9351                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9352                if let Some(ref clustered) = modifiers.clustered {
9353                    self.write_space();
9354                    self.write_keyword(clustered);
9355                }
9356                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9357                if let Some(ref n) = name {
9358                    if !*has_constraint_keyword {
9359                        self.write_space();
9360                        self.generate_identifier(n)?;
9361                    }
9362                }
9363                self.write(" (");
9364                for (i, col) in columns.iter().enumerate() {
9365                    if i > 0 {
9366                        self.write(", ");
9367                    }
9368                    self.generate_identifier(col)?;
9369                }
9370                self.write(")");
9371                if !include_columns.is_empty() {
9372                    self.write_space();
9373                    self.write_keyword("INCLUDE");
9374                    self.write(" (");
9375                    for (i, col) in include_columns.iter().enumerate() {
9376                        if i > 0 {
9377                            self.write(", ");
9378                        }
9379                        self.generate_identifier(col)?;
9380                    }
9381                    self.write(")");
9382                }
9383                self.generate_constraint_modifiers(modifiers);
9384            }
9385            TableConstraint::Unique {
9386                name,
9387                columns,
9388                columns_parenthesized,
9389                modifiers,
9390                has_constraint_keyword,
9391                nulls_not_distinct,
9392            } => {
9393                if let Some(ref n) = name {
9394                    if *has_constraint_keyword {
9395                        self.write_keyword("CONSTRAINT");
9396                        self.write_space();
9397                        self.generate_identifier(n)?;
9398                        self.write_space();
9399                    }
9400                }
9401                self.write_keyword("UNIQUE");
9402                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9403                if let Some(ref clustered) = modifiers.clustered {
9404                    self.write_space();
9405                    self.write_keyword(clustered);
9406                }
9407                // PostgreSQL 15+: NULLS NOT DISTINCT
9408                if *nulls_not_distinct {
9409                    self.write(" NULLS NOT DISTINCT");
9410                }
9411                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9412                if let Some(ref n) = name {
9413                    if !*has_constraint_keyword {
9414                        self.write_space();
9415                        self.generate_identifier(n)?;
9416                    }
9417                }
9418                if *columns_parenthesized {
9419                    self.write(" (");
9420                    for (i, col) in columns.iter().enumerate() {
9421                        if i > 0 {
9422                            self.write(", ");
9423                        }
9424                        self.generate_identifier(col)?;
9425                    }
9426                    self.write(")");
9427                } else {
9428                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9429                    for col in columns.iter() {
9430                        self.write_space();
9431                        self.generate_identifier(col)?;
9432                    }
9433                }
9434                self.generate_constraint_modifiers(modifiers);
9435            }
9436            TableConstraint::ForeignKey {
9437                name,
9438                columns,
9439                references,
9440                on_delete,
9441                on_update,
9442                modifiers,
9443            } => {
9444                if let Some(ref n) = name {
9445                    self.write_keyword("CONSTRAINT");
9446                    self.write_space();
9447                    self.generate_identifier(n)?;
9448                    self.write_space();
9449                }
9450                self.write_keyword("FOREIGN KEY");
9451                self.write(" (");
9452                for (i, col) in columns.iter().enumerate() {
9453                    if i > 0 {
9454                        self.write(", ");
9455                    }
9456                    self.generate_identifier(col)?;
9457                }
9458                self.write(")");
9459                if let Some(ref refs) = references {
9460                    self.write(" ");
9461                    self.write_keyword("REFERENCES");
9462                    self.write_space();
9463                    self.generate_table(&refs.table)?;
9464                    if !refs.columns.is_empty() {
9465                        if self.config.pretty {
9466                            self.write(" (");
9467                            self.write_newline();
9468                            self.indent_level += 1;
9469                            for (i, col) in refs.columns.iter().enumerate() {
9470                                if i > 0 {
9471                                    self.write(",");
9472                                    self.write_newline();
9473                                }
9474                                self.write_indent();
9475                                self.generate_identifier(col)?;
9476                            }
9477                            self.indent_level -= 1;
9478                            self.write_newline();
9479                            self.write_indent();
9480                            self.write(")");
9481                        } else {
9482                            self.write(" (");
9483                            for (i, col) in refs.columns.iter().enumerate() {
9484                                if i > 0 {
9485                                    self.write(", ");
9486                                }
9487                                self.generate_identifier(col)?;
9488                            }
9489                            self.write(")");
9490                        }
9491                    }
9492                    self.generate_referential_actions(refs)?;
9493                } else {
9494                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9495                    if let Some(ref action) = on_delete {
9496                        self.write_space();
9497                        self.write_keyword("ON DELETE");
9498                        self.write_space();
9499                        self.generate_referential_action(action);
9500                    }
9501                    if let Some(ref action) = on_update {
9502                        self.write_space();
9503                        self.write_keyword("ON UPDATE");
9504                        self.write_space();
9505                        self.generate_referential_action(action);
9506                    }
9507                }
9508                self.generate_constraint_modifiers(modifiers);
9509            }
9510            TableConstraint::Check {
9511                name,
9512                expression,
9513                modifiers,
9514            } => {
9515                if let Some(ref n) = name {
9516                    self.write_keyword("CONSTRAINT");
9517                    self.write_space();
9518                    self.generate_identifier(n)?;
9519                    self.write_space();
9520                }
9521                self.write_keyword("CHECK");
9522                self.write(" (");
9523                self.generate_expression(expression)?;
9524                self.write(")");
9525                self.generate_constraint_modifiers(modifiers);
9526            }
9527            TableConstraint::Assume { name, expression } => {
9528                if let Some(ref n) = name {
9529                    self.write_keyword("CONSTRAINT");
9530                    self.write_space();
9531                    self.generate_identifier(n)?;
9532                    self.write_space();
9533                }
9534                self.write_keyword("ASSUME");
9535                self.write(" (");
9536                self.generate_expression(expression)?;
9537                self.write(")");
9538            }
9539            TableConstraint::Default {
9540                name,
9541                expression,
9542                column,
9543            } => {
9544                if let Some(ref n) = name {
9545                    self.write_keyword("CONSTRAINT");
9546                    self.write_space();
9547                    self.generate_identifier(n)?;
9548                    self.write_space();
9549                }
9550                self.write_keyword("DEFAULT");
9551                self.write_space();
9552                self.generate_expression(expression)?;
9553                self.write_space();
9554                self.write_keyword("FOR");
9555                self.write_space();
9556                self.generate_identifier(column)?;
9557            }
9558            TableConstraint::Index {
9559                name,
9560                columns,
9561                kind,
9562                modifiers,
9563                use_key_keyword,
9564                expression,
9565                index_type,
9566                granularity,
9567            } => {
9568                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9569                if expression.is_some() {
9570                    self.write_keyword("INDEX");
9571                    if let Some(ref n) = name {
9572                        self.write_space();
9573                        self.generate_identifier(n)?;
9574                    }
9575                    if let Some(ref expr) = expression {
9576                        self.write_space();
9577                        self.generate_expression(expr)?;
9578                    }
9579                    if let Some(ref idx_type) = index_type {
9580                        self.write_space();
9581                        self.write_keyword("TYPE");
9582                        self.write_space();
9583                        self.generate_expression(idx_type)?;
9584                    }
9585                    if let Some(ref gran) = granularity {
9586                        self.write_space();
9587                        self.write_keyword("GRANULARITY");
9588                        self.write_space();
9589                        self.generate_expression(gran)?;
9590                    }
9591                } else {
9592                    // Standard INDEX syntax
9593                    // Determine the index keyword to use
9594                    // MySQL normalizes KEY to INDEX
9595                    use crate::dialects::DialectType;
9596                    let index_keyword = if *use_key_keyword
9597                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9598                    {
9599                        "KEY"
9600                    } else {
9601                        "INDEX"
9602                    };
9603
9604                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9605                    if let Some(ref k) = kind {
9606                        self.write_keyword(k);
9607                        // For UNIQUE, don't add INDEX/KEY keyword
9608                        if k != "UNIQUE" {
9609                            self.write_space();
9610                            self.write_keyword(index_keyword);
9611                        }
9612                    } else {
9613                        self.write_keyword(index_keyword);
9614                    }
9615
9616                    // Output USING before name if using_before_columns is true and there's no name
9617                    if modifiers.using_before_columns && name.is_none() {
9618                        if let Some(ref using) = modifiers.using {
9619                            self.write_space();
9620                            self.write_keyword("USING");
9621                            self.write_space();
9622                            self.write_keyword(using);
9623                        }
9624                    }
9625
9626                    // Output index name if present
9627                    if let Some(ref n) = name {
9628                        self.write_space();
9629                        self.generate_identifier(n)?;
9630                    }
9631
9632                    // Output USING after name but before columns if using_before_columns and there's a name
9633                    if modifiers.using_before_columns && name.is_some() {
9634                        if let Some(ref using) = modifiers.using {
9635                            self.write_space();
9636                            self.write_keyword("USING");
9637                            self.write_space();
9638                            self.write_keyword(using);
9639                        }
9640                    }
9641
9642                    // Output columns
9643                    self.write(" (");
9644                    for (i, col) in columns.iter().enumerate() {
9645                        if i > 0 {
9646                            self.write(", ");
9647                        }
9648                        self.generate_identifier(col)?;
9649                    }
9650                    self.write(")");
9651
9652                    // Output USING after columns if not using_before_columns
9653                    if !modifiers.using_before_columns {
9654                        if let Some(ref using) = modifiers.using {
9655                            self.write_space();
9656                            self.write_keyword("USING");
9657                            self.write_space();
9658                            self.write_keyword(using);
9659                        }
9660                    }
9661
9662                    // Output other constraint modifiers (but skip USING since we already handled it)
9663                    self.generate_constraint_modifiers_without_using(modifiers);
9664                }
9665            }
9666            TableConstraint::Projection { name, expression } => {
9667                // ClickHouse: PROJECTION name (SELECT ...)
9668                self.write_keyword("PROJECTION");
9669                self.write_space();
9670                self.generate_identifier(name)?;
9671                self.write(" (");
9672                self.generate_expression(expression)?;
9673                self.write(")");
9674            }
9675            TableConstraint::Like { source, options } => {
9676                self.write_keyword("LIKE");
9677                self.write_space();
9678                self.generate_table(source)?;
9679                for (action, prop) in options {
9680                    self.write_space();
9681                    match action {
9682                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9683                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9684                    }
9685                    self.write_space();
9686                    self.write_keyword(prop);
9687                }
9688            }
9689            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9690                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9691                self.write(" (");
9692                self.generate_identifier(start_col)?;
9693                self.write(", ");
9694                self.generate_identifier(end_col)?;
9695                self.write(")");
9696            }
9697            TableConstraint::Exclude {
9698                name,
9699                using,
9700                elements,
9701                include_columns,
9702                where_clause,
9703                with_params,
9704                using_index_tablespace,
9705                modifiers: _,
9706            } => {
9707                if let Some(ref n) = name {
9708                    self.write_keyword("CONSTRAINT");
9709                    self.write_space();
9710                    self.generate_identifier(n)?;
9711                    self.write_space();
9712                }
9713                self.write_keyword("EXCLUDE");
9714                if let Some(ref method) = using {
9715                    self.write_space();
9716                    self.write_keyword("USING");
9717                    self.write_space();
9718                    self.write(method);
9719                    self.write("(");
9720                } else {
9721                    self.write(" (");
9722                }
9723                for (i, elem) in elements.iter().enumerate() {
9724                    if i > 0 {
9725                        self.write(", ");
9726                    }
9727                    self.write(&elem.expression);
9728                    self.write_space();
9729                    self.write_keyword("WITH");
9730                    self.write_space();
9731                    self.write(&elem.operator);
9732                }
9733                self.write(")");
9734                if !include_columns.is_empty() {
9735                    self.write_space();
9736                    self.write_keyword("INCLUDE");
9737                    self.write(" (");
9738                    for (i, col) in include_columns.iter().enumerate() {
9739                        if i > 0 {
9740                            self.write(", ");
9741                        }
9742                        self.generate_identifier(col)?;
9743                    }
9744                    self.write(")");
9745                }
9746                if !with_params.is_empty() {
9747                    self.write_space();
9748                    self.write_keyword("WITH");
9749                    self.write(" (");
9750                    for (i, (key, val)) in with_params.iter().enumerate() {
9751                        if i > 0 {
9752                            self.write(", ");
9753                        }
9754                        self.write(key);
9755                        self.write("=");
9756                        self.write(val);
9757                    }
9758                    self.write(")");
9759                }
9760                if let Some(ref tablespace) = using_index_tablespace {
9761                    self.write_space();
9762                    self.write_keyword("USING INDEX TABLESPACE");
9763                    self.write_space();
9764                    self.write(tablespace);
9765                }
9766                if let Some(ref where_expr) = where_clause {
9767                    self.write_space();
9768                    self.write_keyword("WHERE");
9769                    self.write(" (");
9770                    self.generate_expression(where_expr)?;
9771                    self.write(")");
9772                }
9773            }
9774            TableConstraint::Tags(tags) => {
9775                self.write_keyword("TAG");
9776                self.write(" (");
9777                for (i, expr) in tags.expressions.iter().enumerate() {
9778                    if i > 0 {
9779                        self.write(", ");
9780                    }
9781                    self.generate_expression(expr)?;
9782                }
9783                self.write(")");
9784            }
9785            TableConstraint::InitiallyDeferred { deferred } => {
9786                self.write_keyword("INITIALLY");
9787                self.write_space();
9788                if *deferred {
9789                    self.write_keyword("DEFERRED");
9790                } else {
9791                    self.write_keyword("IMMEDIATE");
9792                }
9793            }
9794        }
9795        Ok(())
9796    }
9797
9798    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9799        // Output USING BTREE/HASH (MySQL) - comes first
9800        if let Some(using) = &modifiers.using {
9801            self.write_space();
9802            self.write_keyword("USING");
9803            self.write_space();
9804            self.write_keyword(using);
9805        }
9806        // Output ENFORCED/NOT ENFORCED
9807        if let Some(enforced) = modifiers.enforced {
9808            self.write_space();
9809            if enforced {
9810                self.write_keyword("ENFORCED");
9811            } else {
9812                self.write_keyword("NOT ENFORCED");
9813            }
9814        }
9815        // Output DEFERRABLE/NOT DEFERRABLE
9816        if let Some(deferrable) = modifiers.deferrable {
9817            self.write_space();
9818            if deferrable {
9819                self.write_keyword("DEFERRABLE");
9820            } else {
9821                self.write_keyword("NOT DEFERRABLE");
9822            }
9823        }
9824        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9825        if let Some(initially_deferred) = modifiers.initially_deferred {
9826            self.write_space();
9827            if initially_deferred {
9828                self.write_keyword("INITIALLY DEFERRED");
9829            } else {
9830                self.write_keyword("INITIALLY IMMEDIATE");
9831            }
9832        }
9833        // Output NORELY
9834        if modifiers.norely {
9835            self.write_space();
9836            self.write_keyword("NORELY");
9837        }
9838        // Output RELY
9839        if modifiers.rely {
9840            self.write_space();
9841            self.write_keyword("RELY");
9842        }
9843        // Output NOT VALID (PostgreSQL)
9844        if modifiers.not_valid {
9845            self.write_space();
9846            self.write_keyword("NOT VALID");
9847        }
9848        // Output ON CONFLICT (SQLite)
9849        if let Some(on_conflict) = &modifiers.on_conflict {
9850            self.write_space();
9851            self.write_keyword("ON CONFLICT");
9852            self.write_space();
9853            self.write_keyword(on_conflict);
9854        }
9855        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9856        if !modifiers.with_options.is_empty() {
9857            self.write_space();
9858            self.write_keyword("WITH");
9859            self.write(" (");
9860            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9861                if i > 0 {
9862                    self.write(", ");
9863                }
9864                self.write(key);
9865                self.write("=");
9866                self.write(value);
9867            }
9868            self.write(")");
9869        }
9870        // Output TSQL ON filegroup
9871        if let Some(ref fg) = modifiers.on_filegroup {
9872            self.write_space();
9873            self.write_keyword("ON");
9874            self.write_space();
9875            let _ = self.generate_identifier(fg);
9876        }
9877    }
9878
9879    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9880    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9881        // Output ENFORCED/NOT ENFORCED
9882        if let Some(enforced) = modifiers.enforced {
9883            self.write_space();
9884            if enforced {
9885                self.write_keyword("ENFORCED");
9886            } else {
9887                self.write_keyword("NOT ENFORCED");
9888            }
9889        }
9890        // Output DEFERRABLE/NOT DEFERRABLE
9891        if let Some(deferrable) = modifiers.deferrable {
9892            self.write_space();
9893            if deferrable {
9894                self.write_keyword("DEFERRABLE");
9895            } else {
9896                self.write_keyword("NOT DEFERRABLE");
9897            }
9898        }
9899        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9900        if let Some(initially_deferred) = modifiers.initially_deferred {
9901            self.write_space();
9902            if initially_deferred {
9903                self.write_keyword("INITIALLY DEFERRED");
9904            } else {
9905                self.write_keyword("INITIALLY IMMEDIATE");
9906            }
9907        }
9908        // Output NORELY
9909        if modifiers.norely {
9910            self.write_space();
9911            self.write_keyword("NORELY");
9912        }
9913        // Output RELY
9914        if modifiers.rely {
9915            self.write_space();
9916            self.write_keyword("RELY");
9917        }
9918        // Output NOT VALID (PostgreSQL)
9919        if modifiers.not_valid {
9920            self.write_space();
9921            self.write_keyword("NOT VALID");
9922        }
9923        // Output ON CONFLICT (SQLite)
9924        if let Some(on_conflict) = &modifiers.on_conflict {
9925            self.write_space();
9926            self.write_keyword("ON CONFLICT");
9927            self.write_space();
9928            self.write_keyword(on_conflict);
9929        }
9930        // Output MySQL index-specific modifiers
9931        self.generate_index_specific_modifiers(modifiers);
9932    }
9933
9934    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9935    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9936        if let Some(ref comment) = modifiers.comment {
9937            self.write_space();
9938            self.write_keyword("COMMENT");
9939            self.write(" '");
9940            self.write(comment);
9941            self.write("'");
9942        }
9943        if let Some(visible) = modifiers.visible {
9944            self.write_space();
9945            if visible {
9946                self.write_keyword("VISIBLE");
9947            } else {
9948                self.write_keyword("INVISIBLE");
9949            }
9950        }
9951        if let Some(ref attr) = modifiers.engine_attribute {
9952            self.write_space();
9953            self.write_keyword("ENGINE_ATTRIBUTE");
9954            self.write(" = '");
9955            self.write(attr);
9956            self.write("'");
9957        }
9958        if let Some(ref parser) = modifiers.with_parser {
9959            self.write_space();
9960            self.write_keyword("WITH PARSER");
9961            self.write_space();
9962            self.write(parser);
9963        }
9964    }
9965
9966    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9967        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9968        if !fk_ref.match_after_actions {
9969            if let Some(ref match_type) = fk_ref.match_type {
9970                self.write_space();
9971                self.write_keyword("MATCH");
9972                self.write_space();
9973                match match_type {
9974                    MatchType::Full => self.write_keyword("FULL"),
9975                    MatchType::Partial => self.write_keyword("PARTIAL"),
9976                    MatchType::Simple => self.write_keyword("SIMPLE"),
9977                }
9978            }
9979        }
9980
9981        // Output ON UPDATE and ON DELETE in the original order
9982        if fk_ref.on_update_first {
9983            if let Some(ref action) = fk_ref.on_update {
9984                self.write_space();
9985                self.write_keyword("ON UPDATE");
9986                self.write_space();
9987                self.generate_referential_action(action);
9988            }
9989            if let Some(ref action) = fk_ref.on_delete {
9990                self.write_space();
9991                self.write_keyword("ON DELETE");
9992                self.write_space();
9993                self.generate_referential_action(action);
9994            }
9995        } else {
9996            if let Some(ref action) = fk_ref.on_delete {
9997                self.write_space();
9998                self.write_keyword("ON DELETE");
9999                self.write_space();
10000                self.generate_referential_action(action);
10001            }
10002            if let Some(ref action) = fk_ref.on_update {
10003                self.write_space();
10004                self.write_keyword("ON UPDATE");
10005                self.write_space();
10006                self.generate_referential_action(action);
10007            }
10008        }
10009
10010        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10011        if fk_ref.match_after_actions {
10012            if let Some(ref match_type) = fk_ref.match_type {
10013                self.write_space();
10014                self.write_keyword("MATCH");
10015                self.write_space();
10016                match match_type {
10017                    MatchType::Full => self.write_keyword("FULL"),
10018                    MatchType::Partial => self.write_keyword("PARTIAL"),
10019                    MatchType::Simple => self.write_keyword("SIMPLE"),
10020                }
10021            }
10022        }
10023
10024        // DEFERRABLE / NOT DEFERRABLE
10025        if let Some(deferrable) = fk_ref.deferrable {
10026            self.write_space();
10027            if deferrable {
10028                self.write_keyword("DEFERRABLE");
10029            } else {
10030                self.write_keyword("NOT DEFERRABLE");
10031            }
10032        }
10033
10034        Ok(())
10035    }
10036
10037    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10038        match action {
10039            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10040            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10041            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10042            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10043            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10044        }
10045    }
10046
10047    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10048        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10049        if let Some(ref object_id_args) = dt.object_id_args {
10050            if matches!(
10051                self.config.dialect,
10052                Some(crate::dialects::DialectType::TSQL)
10053                    | Some(crate::dialects::DialectType::Fabric)
10054            ) {
10055                self.write_keyword("IF NOT OBJECT_ID");
10056                self.write("(");
10057                self.write(object_id_args);
10058                self.write(")");
10059                self.write_space();
10060                self.write_keyword("IS NULL BEGIN DROP TABLE");
10061                self.write_space();
10062                for (i, table) in dt.names.iter().enumerate() {
10063                    if i > 0 {
10064                        self.write(", ");
10065                    }
10066                    self.generate_table(table)?;
10067                }
10068                self.write("; ");
10069                self.write_keyword("END");
10070                return Ok(());
10071            }
10072        }
10073
10074        // Athena: DROP TABLE uses Hive engine (backticks)
10075        let saved_athena_hive_context = self.athena_hive_context;
10076        if matches!(
10077            self.config.dialect,
10078            Some(crate::dialects::DialectType::Athena)
10079        ) {
10080            self.athena_hive_context = true;
10081        }
10082
10083        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10084        for comment in &dt.leading_comments {
10085            self.write_formatted_comment(comment);
10086            self.write_space();
10087        }
10088        if dt.iceberg {
10089            self.write_keyword("DROP ICEBERG TABLE");
10090        } else {
10091            self.write_keyword("DROP TABLE");
10092        }
10093
10094        if dt.if_exists {
10095            self.write_space();
10096            self.write_keyword("IF EXISTS");
10097        }
10098
10099        self.write_space();
10100        for (i, table) in dt.names.iter().enumerate() {
10101            if i > 0 {
10102                self.write(", ");
10103            }
10104            self.generate_table(table)?;
10105        }
10106
10107        if dt.cascade_constraints {
10108            self.write_space();
10109            self.write_keyword("CASCADE CONSTRAINTS");
10110        } else if dt.cascade {
10111            self.write_space();
10112            self.write_keyword("CASCADE");
10113        }
10114
10115        if dt.restrict {
10116            self.write_space();
10117            self.write_keyword("RESTRICT");
10118        }
10119
10120        if dt.purge {
10121            self.write_space();
10122            self.write_keyword("PURGE");
10123        }
10124
10125        if dt.sync {
10126            self.write_space();
10127            self.write_keyword("SYNC");
10128        }
10129
10130        // Restore Athena Hive context
10131        self.athena_hive_context = saved_athena_hive_context;
10132
10133        Ok(())
10134    }
10135
10136    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10137        self.write_keyword("UNDROP");
10138        self.write_space();
10139        self.write_keyword(&u.kind);
10140        if u.if_exists {
10141            self.write_space();
10142            self.write_keyword("IF EXISTS");
10143        }
10144        self.write_space();
10145        self.generate_table(&u.name)?;
10146        Ok(())
10147    }
10148
10149    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10150        // Athena: ALTER TABLE uses Hive engine (backticks)
10151        let saved_athena_hive_context = self.athena_hive_context;
10152        if matches!(
10153            self.config.dialect,
10154            Some(crate::dialects::DialectType::Athena)
10155        ) {
10156            self.athena_hive_context = true;
10157        }
10158
10159        self.write_keyword("ALTER");
10160        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10161        if let Some(ref modifier) = at.table_modifier {
10162            if !matches!(
10163                self.config.dialect,
10164                Some(crate::dialects::DialectType::DuckDB)
10165            ) {
10166                self.write_space();
10167                self.write_keyword(modifier);
10168            }
10169        }
10170        self.write(" ");
10171        self.write_keyword("TABLE");
10172        if at.if_exists {
10173            self.write_space();
10174            self.write_keyword("IF EXISTS");
10175        }
10176        self.write_space();
10177        self.generate_table(&at.name)?;
10178
10179        // ClickHouse: ON CLUSTER clause
10180        if let Some(ref on_cluster) = at.on_cluster {
10181            self.write_space();
10182            self.generate_on_cluster(on_cluster)?;
10183        }
10184
10185        // Hive: PARTITION(key=value, ...) clause
10186        if let Some(ref partition) = at.partition {
10187            self.write_space();
10188            self.write_keyword("PARTITION");
10189            self.write("(");
10190            for (i, (key, value)) in partition.iter().enumerate() {
10191                if i > 0 {
10192                    self.write(", ");
10193                }
10194                self.generate_identifier(key)?;
10195                self.write(" = ");
10196                self.generate_expression(value)?;
10197            }
10198            self.write(")");
10199        }
10200
10201        // TSQL: WITH CHECK / WITH NOCHECK modifier
10202        if let Some(ref with_check) = at.with_check {
10203            self.write_space();
10204            self.write_keyword(with_check);
10205        }
10206
10207        if self.config.pretty {
10208            // In pretty mode, format actions with newlines and indentation
10209            self.write_newline();
10210            self.indent_level += 1;
10211            for (i, action) in at.actions.iter().enumerate() {
10212                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10213                let is_continuation = i > 0
10214                    && matches!(
10215                        (&at.actions[i - 1], action),
10216                        (
10217                            AlterTableAction::AddColumn { .. },
10218                            AlterTableAction::AddColumn { .. }
10219                        ) | (
10220                            AlterTableAction::AddConstraint(_),
10221                            AlterTableAction::AddConstraint(_)
10222                        )
10223                    );
10224                if i > 0 {
10225                    self.write(",");
10226                    self.write_newline();
10227                }
10228                self.write_indent();
10229                self.generate_alter_action_with_continuation(action, is_continuation)?;
10230            }
10231            self.indent_level -= 1;
10232        } else {
10233            for (i, action) in at.actions.iter().enumerate() {
10234                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10235                let is_continuation = i > 0
10236                    && matches!(
10237                        (&at.actions[i - 1], action),
10238                        (
10239                            AlterTableAction::AddColumn { .. },
10240                            AlterTableAction::AddColumn { .. }
10241                        ) | (
10242                            AlterTableAction::AddConstraint(_),
10243                            AlterTableAction::AddConstraint(_)
10244                        )
10245                    );
10246                if i > 0 {
10247                    self.write(",");
10248                }
10249                self.write_space();
10250                self.generate_alter_action_with_continuation(action, is_continuation)?;
10251            }
10252        }
10253
10254        // MySQL ALTER TABLE trailing options
10255        if let Some(ref algorithm) = at.algorithm {
10256            self.write(", ");
10257            self.write_keyword("ALGORITHM");
10258            self.write("=");
10259            self.write_keyword(algorithm);
10260        }
10261        if let Some(ref lock) = at.lock {
10262            self.write(", ");
10263            self.write_keyword("LOCK");
10264            self.write("=");
10265            self.write_keyword(lock);
10266        }
10267
10268        // Restore Athena Hive context
10269        self.athena_hive_context = saved_athena_hive_context;
10270
10271        Ok(())
10272    }
10273
10274    fn generate_alter_action_with_continuation(
10275        &mut self,
10276        action: &AlterTableAction,
10277        is_continuation: bool,
10278    ) -> Result<()> {
10279        match action {
10280            AlterTableAction::AddColumn {
10281                column,
10282                if_not_exists,
10283                position,
10284            } => {
10285                use crate::dialects::DialectType;
10286                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10287                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10288                // For other dialects, repeat ADD COLUMN for each
10289                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10290                let is_tsql_like = matches!(
10291                    self.config.dialect,
10292                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10293                );
10294                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10295                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10296
10297                if is_continuation && (is_snowflake || is_tsql_like) {
10298                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10299                } else if is_snowflake {
10300                    self.write_keyword("ADD");
10301                    self.write_space();
10302                } else if is_athena {
10303                    // Athena uses ADD COLUMNS (col_def) syntax
10304                    self.write_keyword("ADD COLUMNS");
10305                    self.write(" (");
10306                } else if self.config.alter_table_include_column_keyword {
10307                    self.write_keyword("ADD COLUMN");
10308                    self.write_space();
10309                } else {
10310                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10311                    self.write_keyword("ADD");
10312                    self.write_space();
10313                }
10314
10315                if *if_not_exists {
10316                    self.write_keyword("IF NOT EXISTS");
10317                    self.write_space();
10318                }
10319                self.generate_column_def(column)?;
10320
10321                // Close parenthesis for Athena
10322                if is_athena {
10323                    self.write(")");
10324                }
10325
10326                // Column position (FIRST or AFTER)
10327                if let Some(pos) = position {
10328                    self.write_space();
10329                    match pos {
10330                        ColumnPosition::First => self.write_keyword("FIRST"),
10331                        ColumnPosition::After(col_name) => {
10332                            self.write_keyword("AFTER");
10333                            self.write_space();
10334                            self.generate_identifier(col_name)?;
10335                        }
10336                    }
10337                }
10338            }
10339            AlterTableAction::DropColumn {
10340                name,
10341                if_exists,
10342                cascade,
10343            } => {
10344                self.write_keyword("DROP COLUMN");
10345                if *if_exists {
10346                    self.write_space();
10347                    self.write_keyword("IF EXISTS");
10348                }
10349                self.write_space();
10350                self.generate_identifier(name)?;
10351                if *cascade {
10352                    self.write_space();
10353                    self.write_keyword("CASCADE");
10354                }
10355            }
10356            AlterTableAction::DropColumns { names } => {
10357                self.write_keyword("DROP COLUMNS");
10358                self.write(" (");
10359                for (i, name) in names.iter().enumerate() {
10360                    if i > 0 {
10361                        self.write(", ");
10362                    }
10363                    self.generate_identifier(name)?;
10364                }
10365                self.write(")");
10366            }
10367            AlterTableAction::RenameColumn {
10368                old_name,
10369                new_name,
10370                if_exists,
10371            } => {
10372                self.write_keyword("RENAME COLUMN");
10373                if *if_exists {
10374                    self.write_space();
10375                    self.write_keyword("IF EXISTS");
10376                }
10377                self.write_space();
10378                self.generate_identifier(old_name)?;
10379                self.write_space();
10380                self.write_keyword("TO");
10381                self.write_space();
10382                self.generate_identifier(new_name)?;
10383            }
10384            AlterTableAction::AlterColumn {
10385                name,
10386                action,
10387                use_modify_keyword,
10388            } => {
10389                use crate::dialects::DialectType;
10390                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10391                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10392                let use_modify = *use_modify_keyword
10393                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10394                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10395                if use_modify {
10396                    self.write_keyword("MODIFY COLUMN");
10397                    self.write_space();
10398                    self.generate_identifier(name)?;
10399                    // For MODIFY COLUMN, output the type directly
10400                    if let AlterColumnAction::SetDataType {
10401                        data_type,
10402                        using: _,
10403                        collate,
10404                    } = action
10405                    {
10406                        self.write_space();
10407                        self.generate_data_type(data_type)?;
10408                        // Output COLLATE clause if present
10409                        if let Some(collate_name) = collate {
10410                            self.write_space();
10411                            self.write_keyword("COLLATE");
10412                            self.write_space();
10413                            // Output as single-quoted string
10414                            self.write(&format!("'{}'", collate_name));
10415                        }
10416                    } else {
10417                        self.write_space();
10418                        self.generate_alter_column_action(action)?;
10419                    }
10420                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10421                    && matches!(action, AlterColumnAction::SetDataType { .. })
10422                {
10423                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10424                    self.write_keyword("CHANGE COLUMN");
10425                    self.write_space();
10426                    self.generate_identifier(name)?;
10427                    self.write_space();
10428                    self.generate_identifier(name)?;
10429                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10430                        self.write_space();
10431                        self.generate_data_type(data_type)?;
10432                    }
10433                } else {
10434                    self.write_keyword("ALTER COLUMN");
10435                    self.write_space();
10436                    self.generate_identifier(name)?;
10437                    self.write_space();
10438                    self.generate_alter_column_action(action)?;
10439                }
10440            }
10441            AlterTableAction::RenameTable(new_name) => {
10442                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10443                let mysql_like = matches!(
10444                    self.config.dialect,
10445                    Some(DialectType::MySQL)
10446                        | Some(DialectType::Doris)
10447                        | Some(DialectType::StarRocks)
10448                        | Some(DialectType::SingleStore)
10449                );
10450                if mysql_like {
10451                    self.write_keyword("RENAME");
10452                } else {
10453                    self.write_keyword("RENAME TO");
10454                }
10455                self.write_space();
10456                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10457                let rename_table_with_db = !matches!(
10458                    self.config.dialect,
10459                    Some(DialectType::Doris)
10460                        | Some(DialectType::DuckDB)
10461                        | Some(DialectType::BigQuery)
10462                        | Some(DialectType::PostgreSQL)
10463                );
10464                if !rename_table_with_db {
10465                    let mut stripped = new_name.clone();
10466                    stripped.schema = None;
10467                    stripped.catalog = None;
10468                    self.generate_table(&stripped)?;
10469                } else {
10470                    self.generate_table(new_name)?;
10471                }
10472            }
10473            AlterTableAction::AddConstraint(constraint) => {
10474                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10475                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10476                if !is_continuation {
10477                    self.write_keyword("ADD");
10478                    self.write_space();
10479                }
10480                self.generate_table_constraint(constraint)?;
10481            }
10482            AlterTableAction::DropConstraint { name, if_exists } => {
10483                self.write_keyword("DROP CONSTRAINT");
10484                if *if_exists {
10485                    self.write_space();
10486                    self.write_keyword("IF EXISTS");
10487                }
10488                self.write_space();
10489                self.generate_identifier(name)?;
10490            }
10491            AlterTableAction::DropForeignKey { name } => {
10492                self.write_keyword("DROP FOREIGN KEY");
10493                self.write_space();
10494                self.generate_identifier(name)?;
10495            }
10496            AlterTableAction::DropPartition {
10497                partitions,
10498                if_exists,
10499            } => {
10500                self.write_keyword("DROP");
10501                if *if_exists {
10502                    self.write_space();
10503                    self.write_keyword("IF EXISTS");
10504                }
10505                for (i, partition) in partitions.iter().enumerate() {
10506                    if i > 0 {
10507                        self.write(",");
10508                    }
10509                    self.write_space();
10510                    self.write_keyword("PARTITION");
10511                    // Check for special ClickHouse partition formats
10512                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10513                        // ClickHouse: PARTITION <expression>
10514                        self.write_space();
10515                        self.generate_expression(&partition[0].1)?;
10516                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10517                        // ClickHouse: PARTITION ALL
10518                        self.write_space();
10519                        self.write_keyword("ALL");
10520                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10521                        // ClickHouse: PARTITION ID 'string'
10522                        self.write_space();
10523                        self.write_keyword("ID");
10524                        self.write_space();
10525                        self.generate_expression(&partition[0].1)?;
10526                    } else {
10527                        // Standard SQL: PARTITION(key=value, ...)
10528                        self.write("(");
10529                        for (j, (key, value)) in partition.iter().enumerate() {
10530                            if j > 0 {
10531                                self.write(", ");
10532                            }
10533                            self.generate_identifier(key)?;
10534                            self.write(" = ");
10535                            self.generate_expression(value)?;
10536                        }
10537                        self.write(")");
10538                    }
10539                }
10540            }
10541            AlterTableAction::Delete { where_clause } => {
10542                self.write_keyword("DELETE");
10543                self.write_space();
10544                self.write_keyword("WHERE");
10545                self.write_space();
10546                self.generate_expression(where_clause)?;
10547            }
10548            AlterTableAction::SwapWith(target) => {
10549                self.write_keyword("SWAP WITH");
10550                self.write_space();
10551                self.generate_table(target)?;
10552            }
10553            AlterTableAction::SetProperty { properties } => {
10554                use crate::dialects::DialectType;
10555                self.write_keyword("SET");
10556                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10557                let is_trino_presto = matches!(
10558                    self.config.dialect,
10559                    Some(DialectType::Trino) | Some(DialectType::Presto)
10560                );
10561                if is_trino_presto {
10562                    self.write_space();
10563                    self.write_keyword("PROPERTIES");
10564                }
10565                let eq = if is_trino_presto { " = " } else { "=" };
10566                for (i, (key, value)) in properties.iter().enumerate() {
10567                    if i > 0 {
10568                        self.write(",");
10569                    }
10570                    self.write_space();
10571                    // Handle quoted property names for Trino
10572                    if key.contains(' ') {
10573                        self.generate_string_literal(key)?;
10574                    } else {
10575                        self.write(key);
10576                    }
10577                    self.write(eq);
10578                    self.generate_expression(value)?;
10579                }
10580            }
10581            AlterTableAction::UnsetProperty { properties } => {
10582                self.write_keyword("UNSET");
10583                for (i, name) in properties.iter().enumerate() {
10584                    if i > 0 {
10585                        self.write(",");
10586                    }
10587                    self.write_space();
10588                    self.write(name);
10589                }
10590            }
10591            AlterTableAction::ClusterBy { expressions } => {
10592                self.write_keyword("CLUSTER BY");
10593                self.write(" (");
10594                for (i, expr) in expressions.iter().enumerate() {
10595                    if i > 0 {
10596                        self.write(", ");
10597                    }
10598                    self.generate_expression(expr)?;
10599                }
10600                self.write(")");
10601            }
10602            AlterTableAction::SetTag { expressions } => {
10603                self.write_keyword("SET TAG");
10604                for (i, (key, value)) in expressions.iter().enumerate() {
10605                    if i > 0 {
10606                        self.write(",");
10607                    }
10608                    self.write_space();
10609                    self.write(key);
10610                    self.write(" = ");
10611                    self.generate_expression(value)?;
10612                }
10613            }
10614            AlterTableAction::UnsetTag { names } => {
10615                self.write_keyword("UNSET TAG");
10616                for (i, name) in names.iter().enumerate() {
10617                    if i > 0 {
10618                        self.write(",");
10619                    }
10620                    self.write_space();
10621                    self.write(name);
10622                }
10623            }
10624            AlterTableAction::SetOptions { expressions } => {
10625                self.write_keyword("SET");
10626                self.write(" (");
10627                for (i, expr) in expressions.iter().enumerate() {
10628                    if i > 0 {
10629                        self.write(", ");
10630                    }
10631                    self.generate_expression(expr)?;
10632                }
10633                self.write(")");
10634            }
10635            AlterTableAction::AlterIndex { name, visible } => {
10636                self.write_keyword("ALTER INDEX");
10637                self.write_space();
10638                self.generate_identifier(name)?;
10639                self.write_space();
10640                if *visible {
10641                    self.write_keyword("VISIBLE");
10642                } else {
10643                    self.write_keyword("INVISIBLE");
10644                }
10645            }
10646            AlterTableAction::SetAttribute { attribute } => {
10647                self.write_keyword("SET");
10648                self.write_space();
10649                self.write_keyword(attribute);
10650            }
10651            AlterTableAction::SetStageFileFormat { options } => {
10652                self.write_keyword("SET");
10653                self.write_space();
10654                self.write_keyword("STAGE_FILE_FORMAT");
10655                self.write(" = (");
10656                if let Some(opts) = options {
10657                    self.generate_space_separated_properties(opts)?;
10658                }
10659                self.write(")");
10660            }
10661            AlterTableAction::SetStageCopyOptions { options } => {
10662                self.write_keyword("SET");
10663                self.write_space();
10664                self.write_keyword("STAGE_COPY_OPTIONS");
10665                self.write(" = (");
10666                if let Some(opts) = options {
10667                    self.generate_space_separated_properties(opts)?;
10668                }
10669                self.write(")");
10670            }
10671            AlterTableAction::AddColumns { columns, cascade } => {
10672                // Oracle uses ADD (...) without COLUMNS keyword
10673                // Hive/Spark uses ADD COLUMNS (...)
10674                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10675                if is_oracle {
10676                    self.write_keyword("ADD");
10677                } else {
10678                    self.write_keyword("ADD COLUMNS");
10679                }
10680                self.write(" (");
10681                for (i, col) in columns.iter().enumerate() {
10682                    if i > 0 {
10683                        self.write(", ");
10684                    }
10685                    self.generate_column_def(col)?;
10686                }
10687                self.write(")");
10688                if *cascade {
10689                    self.write_space();
10690                    self.write_keyword("CASCADE");
10691                }
10692            }
10693            AlterTableAction::ChangeColumn {
10694                old_name,
10695                new_name,
10696                data_type,
10697                comment,
10698                cascade,
10699            } => {
10700                use crate::dialects::DialectType;
10701                let is_spark = matches!(
10702                    self.config.dialect,
10703                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10704                );
10705                let is_rename = old_name.name != new_name.name;
10706
10707                if is_spark {
10708                    if is_rename {
10709                        // Spark: RENAME COLUMN old TO new
10710                        self.write_keyword("RENAME COLUMN");
10711                        self.write_space();
10712                        self.generate_identifier(old_name)?;
10713                        self.write_space();
10714                        self.write_keyword("TO");
10715                        self.write_space();
10716                        self.generate_identifier(new_name)?;
10717                    } else if comment.is_some() {
10718                        // Spark: ALTER COLUMN old COMMENT 'comment'
10719                        self.write_keyword("ALTER COLUMN");
10720                        self.write_space();
10721                        self.generate_identifier(old_name)?;
10722                        self.write_space();
10723                        self.write_keyword("COMMENT");
10724                        self.write_space();
10725                        self.write("'");
10726                        self.write(comment.as_ref().unwrap());
10727                        self.write("'");
10728                    } else if data_type.is_some() {
10729                        // Spark: ALTER COLUMN old TYPE data_type
10730                        self.write_keyword("ALTER COLUMN");
10731                        self.write_space();
10732                        self.generate_identifier(old_name)?;
10733                        self.write_space();
10734                        self.write_keyword("TYPE");
10735                        self.write_space();
10736                        self.generate_data_type(data_type.as_ref().unwrap())?;
10737                    } else {
10738                        // Fallback to CHANGE COLUMN
10739                        self.write_keyword("CHANGE COLUMN");
10740                        self.write_space();
10741                        self.generate_identifier(old_name)?;
10742                        self.write_space();
10743                        self.generate_identifier(new_name)?;
10744                    }
10745                } else {
10746                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10747                    if data_type.is_some() {
10748                        self.write_keyword("CHANGE COLUMN");
10749                    } else {
10750                        self.write_keyword("CHANGE");
10751                    }
10752                    self.write_space();
10753                    self.generate_identifier(old_name)?;
10754                    self.write_space();
10755                    self.generate_identifier(new_name)?;
10756                    if let Some(ref dt) = data_type {
10757                        self.write_space();
10758                        self.generate_data_type(dt)?;
10759                    }
10760                    if let Some(ref c) = comment {
10761                        self.write_space();
10762                        self.write_keyword("COMMENT");
10763                        self.write_space();
10764                        self.write("'");
10765                        self.write(c);
10766                        self.write("'");
10767                    }
10768                    if *cascade {
10769                        self.write_space();
10770                        self.write_keyword("CASCADE");
10771                    }
10772                }
10773            }
10774            AlterTableAction::AddPartition {
10775                partition,
10776                if_not_exists,
10777                location,
10778            } => {
10779                self.write_keyword("ADD");
10780                self.write_space();
10781                if *if_not_exists {
10782                    self.write_keyword("IF NOT EXISTS");
10783                    self.write_space();
10784                }
10785                self.generate_expression(partition)?;
10786                if let Some(ref loc) = location {
10787                    self.write_space();
10788                    self.write_keyword("LOCATION");
10789                    self.write_space();
10790                    self.generate_expression(loc)?;
10791                }
10792            }
10793            AlterTableAction::AlterSortKey {
10794                this,
10795                expressions,
10796                compound,
10797            } => {
10798                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10799                self.write_keyword("ALTER");
10800                if *compound {
10801                    self.write_space();
10802                    self.write_keyword("COMPOUND");
10803                }
10804                self.write_space();
10805                self.write_keyword("SORTKEY");
10806                self.write_space();
10807                if let Some(style) = this {
10808                    self.write_keyword(style);
10809                } else if !expressions.is_empty() {
10810                    self.write("(");
10811                    for (i, expr) in expressions.iter().enumerate() {
10812                        if i > 0 {
10813                            self.write(", ");
10814                        }
10815                        self.generate_expression(expr)?;
10816                    }
10817                    self.write(")");
10818                }
10819            }
10820            AlterTableAction::AlterDistStyle { style, distkey } => {
10821                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10822                self.write_keyword("ALTER");
10823                self.write_space();
10824                self.write_keyword("DISTSTYLE");
10825                self.write_space();
10826                self.write_keyword(style);
10827                if let Some(col) = distkey {
10828                    self.write_space();
10829                    self.write_keyword("DISTKEY");
10830                    self.write_space();
10831                    self.generate_identifier(col)?;
10832                }
10833            }
10834            AlterTableAction::SetTableProperties { properties } => {
10835                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10836                self.write_keyword("SET TABLE PROPERTIES");
10837                self.write(" (");
10838                for (i, (key, value)) in properties.iter().enumerate() {
10839                    if i > 0 {
10840                        self.write(", ");
10841                    }
10842                    self.generate_expression(key)?;
10843                    self.write(" = ");
10844                    self.generate_expression(value)?;
10845                }
10846                self.write(")");
10847            }
10848            AlterTableAction::SetLocation { location } => {
10849                // Redshift: SET LOCATION 's3://bucket/folder/'
10850                self.write_keyword("SET LOCATION");
10851                self.write_space();
10852                self.write("'");
10853                self.write(location);
10854                self.write("'");
10855            }
10856            AlterTableAction::SetFileFormat { format } => {
10857                // Redshift: SET FILE FORMAT AVRO
10858                self.write_keyword("SET FILE FORMAT");
10859                self.write_space();
10860                self.write_keyword(format);
10861            }
10862            AlterTableAction::ReplacePartition { partition, source } => {
10863                // ClickHouse: REPLACE PARTITION expr FROM source
10864                self.write_keyword("REPLACE PARTITION");
10865                self.write_space();
10866                self.generate_expression(partition)?;
10867                if let Some(src) = source {
10868                    self.write_space();
10869                    self.write_keyword("FROM");
10870                    self.write_space();
10871                    self.generate_expression(src)?;
10872                }
10873            }
10874            AlterTableAction::Raw { sql } => {
10875                self.write(sql);
10876            }
10877        }
10878        Ok(())
10879    }
10880
10881    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10882        match action {
10883            AlterColumnAction::SetDataType {
10884                data_type,
10885                using,
10886                collate,
10887            } => {
10888                use crate::dialects::DialectType;
10889                // Dialect-specific type change syntax:
10890                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10891                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10892                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10893                let is_no_prefix = matches!(
10894                    self.config.dialect,
10895                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10896                );
10897                let is_type_only = matches!(
10898                    self.config.dialect,
10899                    Some(DialectType::Redshift)
10900                        | Some(DialectType::Spark)
10901                        | Some(DialectType::Databricks)
10902                );
10903                if is_type_only {
10904                    self.write_keyword("TYPE");
10905                    self.write_space();
10906                } else if !is_no_prefix {
10907                    self.write_keyword("SET DATA TYPE");
10908                    self.write_space();
10909                }
10910                self.generate_data_type(data_type)?;
10911                if let Some(ref collation) = collate {
10912                    self.write_space();
10913                    self.write_keyword("COLLATE");
10914                    self.write_space();
10915                    self.write(collation);
10916                }
10917                if let Some(ref using_expr) = using {
10918                    self.write_space();
10919                    self.write_keyword("USING");
10920                    self.write_space();
10921                    self.generate_expression(using_expr)?;
10922                }
10923            }
10924            AlterColumnAction::SetDefault(expr) => {
10925                self.write_keyword("SET DEFAULT");
10926                self.write_space();
10927                self.generate_expression(expr)?;
10928            }
10929            AlterColumnAction::DropDefault => {
10930                self.write_keyword("DROP DEFAULT");
10931            }
10932            AlterColumnAction::SetNotNull => {
10933                self.write_keyword("SET NOT NULL");
10934            }
10935            AlterColumnAction::DropNotNull => {
10936                self.write_keyword("DROP NOT NULL");
10937            }
10938            AlterColumnAction::Comment(comment) => {
10939                self.write_keyword("COMMENT");
10940                self.write_space();
10941                self.generate_string_literal(comment)?;
10942            }
10943            AlterColumnAction::SetVisible => {
10944                self.write_keyword("SET VISIBLE");
10945            }
10946            AlterColumnAction::SetInvisible => {
10947                self.write_keyword("SET INVISIBLE");
10948            }
10949        }
10950        Ok(())
10951    }
10952
10953    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10954        self.write_keyword("CREATE");
10955
10956        if ci.unique {
10957            self.write_space();
10958            self.write_keyword("UNIQUE");
10959        }
10960
10961        // TSQL CLUSTERED/NONCLUSTERED modifier
10962        if let Some(ref clustered) = ci.clustered {
10963            self.write_space();
10964            self.write_keyword(clustered);
10965        }
10966
10967        self.write_space();
10968        self.write_keyword("INDEX");
10969
10970        // PostgreSQL CONCURRENTLY modifier
10971        if ci.concurrently {
10972            self.write_space();
10973            self.write_keyword("CONCURRENTLY");
10974        }
10975
10976        if ci.if_not_exists {
10977            self.write_space();
10978            self.write_keyword("IF NOT EXISTS");
10979        }
10980
10981        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10982        if !ci.name.name.is_empty() {
10983            self.write_space();
10984            self.generate_identifier(&ci.name)?;
10985        }
10986        self.write_space();
10987        self.write_keyword("ON");
10988        // Hive uses ON TABLE
10989        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10990            self.write_space();
10991            self.write_keyword("TABLE");
10992        }
10993        self.write_space();
10994        self.generate_table(&ci.table)?;
10995
10996        // Column list (optional for COLUMNSTORE indexes)
10997        // Standard SQL convention: ON t(a) without space before paren
10998        if !ci.columns.is_empty() || ci.using.is_some() {
10999            let space_before_paren = false;
11000
11001            if let Some(ref using) = ci.using {
11002                self.write_space();
11003                self.write_keyword("USING");
11004                self.write_space();
11005                self.write(using);
11006                if space_before_paren {
11007                    self.write(" (");
11008                } else {
11009                    self.write("(");
11010                }
11011            } else {
11012                if space_before_paren {
11013                    self.write(" (");
11014                } else {
11015                    self.write("(");
11016                }
11017            }
11018            for (i, col) in ci.columns.iter().enumerate() {
11019                if i > 0 {
11020                    self.write(", ");
11021                }
11022                self.generate_identifier(&col.column)?;
11023                if let Some(ref opclass) = col.opclass {
11024                    self.write_space();
11025                    self.write(opclass);
11026                }
11027                if col.desc {
11028                    self.write_space();
11029                    self.write_keyword("DESC");
11030                } else if col.asc {
11031                    self.write_space();
11032                    self.write_keyword("ASC");
11033                }
11034                if let Some(nulls_first) = col.nulls_first {
11035                    self.write_space();
11036                    self.write_keyword("NULLS");
11037                    self.write_space();
11038                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11039                }
11040            }
11041            self.write(")");
11042        }
11043
11044        // PostgreSQL INCLUDE (col1, col2) clause
11045        if !ci.include_columns.is_empty() {
11046            self.write_space();
11047            self.write_keyword("INCLUDE");
11048            self.write(" (");
11049            for (i, col) in ci.include_columns.iter().enumerate() {
11050                if i > 0 {
11051                    self.write(", ");
11052                }
11053                self.generate_identifier(col)?;
11054            }
11055            self.write(")");
11056        }
11057
11058        // TSQL: WITH (option=value, ...) clause
11059        if !ci.with_options.is_empty() {
11060            self.write_space();
11061            self.write_keyword("WITH");
11062            self.write(" (");
11063            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11064                if i > 0 {
11065                    self.write(", ");
11066                }
11067                self.write(key);
11068                self.write("=");
11069                self.write(value);
11070            }
11071            self.write(")");
11072        }
11073
11074        // PostgreSQL WHERE clause for partial indexes
11075        if let Some(ref where_clause) = ci.where_clause {
11076            self.write_space();
11077            self.write_keyword("WHERE");
11078            self.write_space();
11079            self.generate_expression(where_clause)?;
11080        }
11081
11082        // TSQL: ON filegroup or partition scheme clause
11083        if let Some(ref on_fg) = ci.on_filegroup {
11084            self.write_space();
11085            self.write_keyword("ON");
11086            self.write_space();
11087            self.write(on_fg);
11088        }
11089
11090        Ok(())
11091    }
11092
11093    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11094        self.write_keyword("DROP INDEX");
11095
11096        if di.concurrently {
11097            self.write_space();
11098            self.write_keyword("CONCURRENTLY");
11099        }
11100
11101        if di.if_exists {
11102            self.write_space();
11103            self.write_keyword("IF EXISTS");
11104        }
11105
11106        self.write_space();
11107        self.generate_identifier(&di.name)?;
11108
11109        if let Some(ref table) = di.table {
11110            self.write_space();
11111            self.write_keyword("ON");
11112            self.write_space();
11113            self.generate_table(table)?;
11114        }
11115
11116        Ok(())
11117    }
11118
11119    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11120        self.write_keyword("CREATE");
11121
11122        // MySQL: ALGORITHM=...
11123        if let Some(ref algorithm) = cv.algorithm {
11124            self.write_space();
11125            self.write_keyword("ALGORITHM");
11126            self.write("=");
11127            self.write_keyword(algorithm);
11128        }
11129
11130        // MySQL: DEFINER=...
11131        if let Some(ref definer) = cv.definer {
11132            self.write_space();
11133            self.write_keyword("DEFINER");
11134            self.write("=");
11135            self.write(definer);
11136        }
11137
11138        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11139        if cv.security_sql_style && !cv.security_after_name {
11140            if let Some(ref security) = cv.security {
11141                self.write_space();
11142                self.write_keyword("SQL SECURITY");
11143                self.write_space();
11144                match security {
11145                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11146                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11147                    FunctionSecurity::None => self.write_keyword("NONE"),
11148                }
11149            }
11150        }
11151
11152        if cv.or_alter {
11153            self.write_space();
11154            self.write_keyword("OR ALTER");
11155        } else if cv.or_replace {
11156            self.write_space();
11157            self.write_keyword("OR REPLACE");
11158        }
11159
11160        if cv.temporary {
11161            self.write_space();
11162            self.write_keyword("TEMPORARY");
11163        }
11164
11165        if cv.materialized {
11166            self.write_space();
11167            self.write_keyword("MATERIALIZED");
11168        }
11169
11170        // Snowflake: SECURE VIEW
11171        if cv.secure {
11172            self.write_space();
11173            self.write_keyword("SECURE");
11174        }
11175
11176        self.write_space();
11177        self.write_keyword("VIEW");
11178
11179        if cv.if_not_exists {
11180            self.write_space();
11181            self.write_keyword("IF NOT EXISTS");
11182        }
11183
11184        self.write_space();
11185        self.generate_table(&cv.name)?;
11186
11187        // ClickHouse: ON CLUSTER clause
11188        if let Some(ref on_cluster) = cv.on_cluster {
11189            self.write_space();
11190            self.generate_on_cluster(on_cluster)?;
11191        }
11192
11193        // ClickHouse: TO destination_table
11194        if let Some(ref to_table) = cv.to_table {
11195            self.write_space();
11196            self.write_keyword("TO");
11197            self.write_space();
11198            self.generate_table(to_table)?;
11199        }
11200
11201        // For regular VIEW: columns come before COPY GRANTS
11202        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11203        if !cv.materialized {
11204            // Regular VIEW: columns first
11205            if !cv.columns.is_empty() {
11206                self.write(" (");
11207                for (i, col) in cv.columns.iter().enumerate() {
11208                    if i > 0 {
11209                        self.write(", ");
11210                    }
11211                    self.generate_identifier(&col.name)?;
11212                    // BigQuery: OPTIONS (key=value, ...) on view column
11213                    if !col.options.is_empty() {
11214                        self.write_space();
11215                        self.generate_options_clause(&col.options)?;
11216                    }
11217                    if let Some(ref comment) = col.comment {
11218                        self.write_space();
11219                        self.write_keyword("COMMENT");
11220                        self.write_space();
11221                        self.generate_string_literal(comment)?;
11222                    }
11223                }
11224                self.write(")");
11225            }
11226
11227            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11228            // Also handles SQL SECURITY after view name (security_after_name)
11229            if !cv.security_sql_style || cv.security_after_name {
11230                if let Some(ref security) = cv.security {
11231                    self.write_space();
11232                    if cv.security_sql_style {
11233                        self.write_keyword("SQL SECURITY");
11234                    } else {
11235                        self.write_keyword("SECURITY");
11236                    }
11237                    self.write_space();
11238                    match security {
11239                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11240                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11241                        FunctionSecurity::None => self.write_keyword("NONE"),
11242                    }
11243                }
11244            }
11245
11246            // Snowflake: COPY GRANTS
11247            if cv.copy_grants {
11248                self.write_space();
11249                self.write_keyword("COPY GRANTS");
11250            }
11251        } else {
11252            // MATERIALIZED VIEW: COPY GRANTS first
11253            if cv.copy_grants {
11254                self.write_space();
11255                self.write_keyword("COPY GRANTS");
11256            }
11257
11258            // Doris: If we have a schema (typed columns), generate that instead
11259            if let Some(ref schema) = cv.schema {
11260                self.write(" (");
11261                for (i, expr) in schema.expressions.iter().enumerate() {
11262                    if i > 0 {
11263                        self.write(", ");
11264                    }
11265                    self.generate_expression(expr)?;
11266                }
11267                self.write(")");
11268            } else if !cv.columns.is_empty() {
11269                // Then columns (simple column names without types)
11270                self.write(" (");
11271                for (i, col) in cv.columns.iter().enumerate() {
11272                    if i > 0 {
11273                        self.write(", ");
11274                    }
11275                    self.generate_identifier(&col.name)?;
11276                    // BigQuery: OPTIONS (key=value, ...) on view column
11277                    if !col.options.is_empty() {
11278                        self.write_space();
11279                        self.generate_options_clause(&col.options)?;
11280                    }
11281                    if let Some(ref comment) = col.comment {
11282                        self.write_space();
11283                        self.write_keyword("COMMENT");
11284                        self.write_space();
11285                        self.generate_string_literal(comment)?;
11286                    }
11287                }
11288                self.write(")");
11289            }
11290
11291            // Doris: KEY (columns) for materialized views
11292            if let Some(ref unique_key) = cv.unique_key {
11293                self.write_space();
11294                self.write_keyword("KEY");
11295                self.write(" (");
11296                for (i, expr) in unique_key.expressions.iter().enumerate() {
11297                    if i > 0 {
11298                        self.write(", ");
11299                    }
11300                    self.generate_expression(expr)?;
11301                }
11302                self.write(")");
11303            }
11304        }
11305
11306        if let Some(ref row_access_policy) = cv.row_access_policy {
11307            self.write_space();
11308            self.write_keyword("WITH");
11309            self.write_space();
11310            self.write(row_access_policy);
11311        }
11312
11313        // Snowflake: COMMENT = 'text'
11314        if let Some(ref comment) = cv.comment {
11315            self.write_space();
11316            self.write_keyword("COMMENT");
11317            self.write("=");
11318            self.generate_string_literal(comment)?;
11319        }
11320
11321        // Snowflake: TAG (name='value', ...)
11322        if !cv.tags.is_empty() {
11323            self.write_space();
11324            self.write_keyword("TAG");
11325            self.write(" (");
11326            for (i, (name, value)) in cv.tags.iter().enumerate() {
11327                if i > 0 {
11328                    self.write(", ");
11329                }
11330                self.write(name);
11331                self.write("='");
11332                self.write(value);
11333                self.write("'");
11334            }
11335            self.write(")");
11336        }
11337
11338        // BigQuery: OPTIONS (key=value, ...)
11339        if !cv.options.is_empty() {
11340            self.write_space();
11341            self.generate_options_clause(&cv.options)?;
11342        }
11343
11344        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11345        if let Some(ref build) = cv.build {
11346            self.write_space();
11347            self.write_keyword("BUILD");
11348            self.write_space();
11349            self.write_keyword(build);
11350        }
11351
11352        // Doris: REFRESH clause for materialized views
11353        if let Some(ref refresh) = cv.refresh {
11354            self.write_space();
11355            self.generate_refresh_trigger_property(refresh)?;
11356        }
11357
11358        // Redshift: AUTO REFRESH YES|NO for materialized views
11359        if let Some(auto_refresh) = cv.auto_refresh {
11360            self.write_space();
11361            self.write_keyword("AUTO REFRESH");
11362            self.write_space();
11363            if auto_refresh {
11364                self.write_keyword("YES");
11365            } else {
11366                self.write_keyword("NO");
11367            }
11368        }
11369
11370        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11371        for prop in &cv.table_properties {
11372            self.write_space();
11373            self.generate_expression(prop)?;
11374        }
11375
11376        // ClickHouse: POPULATE / EMPTY before AS
11377        if let Some(ref population) = cv.clickhouse_population {
11378            self.write_space();
11379            self.write_keyword(population);
11380        }
11381
11382        // Only output AS clause if there's a real query (not just NULL placeholder)
11383        if !matches!(&cv.query, Expression::Null(_)) {
11384            self.write_space();
11385            self.write_keyword("AS");
11386            self.write_space();
11387
11388            // Teradata: LOCKING clause (between AS and query)
11389            if let Some(ref mode) = cv.locking_mode {
11390                self.write_keyword("LOCKING");
11391                self.write_space();
11392                self.write_keyword(mode);
11393                if let Some(ref access) = cv.locking_access {
11394                    self.write_space();
11395                    self.write_keyword("FOR");
11396                    self.write_space();
11397                    self.write_keyword(access);
11398                }
11399                self.write_space();
11400            }
11401
11402            if cv.query_parenthesized {
11403                self.write("(");
11404            }
11405            self.generate_expression(&cv.query)?;
11406            if cv.query_parenthesized {
11407                self.write(")");
11408            }
11409        }
11410
11411        // Redshift: WITH NO SCHEMA BINDING (after query)
11412        if cv.no_schema_binding {
11413            self.write_space();
11414            self.write_keyword("WITH NO SCHEMA BINDING");
11415        }
11416
11417        Ok(())
11418    }
11419
11420    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11421        self.write_keyword("DROP");
11422
11423        if dv.materialized {
11424            self.write_space();
11425            self.write_keyword("MATERIALIZED");
11426        }
11427
11428        self.write_space();
11429        self.write_keyword("VIEW");
11430
11431        if dv.if_exists {
11432            self.write_space();
11433            self.write_keyword("IF EXISTS");
11434        }
11435
11436        self.write_space();
11437        self.generate_table(&dv.name)?;
11438
11439        Ok(())
11440    }
11441
11442    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11443        match tr.target {
11444            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11445            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11446        }
11447        if tr.if_exists {
11448            self.write_space();
11449            self.write_keyword("IF EXISTS");
11450        }
11451        self.write_space();
11452        self.generate_table(&tr.table)?;
11453
11454        // ClickHouse: ON CLUSTER clause
11455        if let Some(ref on_cluster) = tr.on_cluster {
11456            self.write_space();
11457            self.generate_on_cluster(on_cluster)?;
11458        }
11459
11460        // Check if first table has a * (multi-table with star)
11461        if !tr.extra_tables.is_empty() {
11462            // Check if the first entry matches the main table (star case)
11463            let skip_first = if let Some(first) = tr.extra_tables.first() {
11464                first.table.name == tr.table.name && first.star
11465            } else {
11466                false
11467            };
11468
11469            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11470            let strip_star = matches!(
11471                self.config.dialect,
11472                Some(crate::dialects::DialectType::PostgreSQL)
11473                    | Some(crate::dialects::DialectType::Redshift)
11474            );
11475            if skip_first && !strip_star {
11476                self.write("*");
11477            }
11478
11479            // Generate additional tables
11480            for (i, entry) in tr.extra_tables.iter().enumerate() {
11481                if i == 0 && skip_first {
11482                    continue; // Already handled the star for first table
11483                }
11484                self.write(", ");
11485                self.generate_table(&entry.table)?;
11486                if entry.star && !strip_star {
11487                    self.write("*");
11488                }
11489            }
11490        }
11491
11492        // RESTART/CONTINUE IDENTITY
11493        if let Some(identity) = &tr.identity {
11494            self.write_space();
11495            match identity {
11496                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11497                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11498            }
11499        }
11500
11501        if tr.cascade {
11502            self.write_space();
11503            self.write_keyword("CASCADE");
11504        }
11505
11506        if tr.restrict {
11507            self.write_space();
11508            self.write_keyword("RESTRICT");
11509        }
11510
11511        // Output Hive PARTITION clause
11512        if let Some(ref partition) = tr.partition {
11513            self.write_space();
11514            self.generate_expression(partition)?;
11515        }
11516
11517        Ok(())
11518    }
11519
11520    fn generate_use(&mut self, u: &Use) -> Result<()> {
11521        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11522        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11523            self.write_keyword("DATABASE");
11524            self.write_space();
11525            self.generate_identifier(&u.this)?;
11526            return Ok(());
11527        }
11528
11529        self.write_keyword("USE");
11530
11531        if let Some(kind) = &u.kind {
11532            self.write_space();
11533            match kind {
11534                UseKind::Database => self.write_keyword("DATABASE"),
11535                UseKind::Schema => self.write_keyword("SCHEMA"),
11536                UseKind::Role => self.write_keyword("ROLE"),
11537                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11538                UseKind::Catalog => self.write_keyword("CATALOG"),
11539                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11540            }
11541        }
11542
11543        self.write_space();
11544        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11545        // without quoting, since these are keywords not identifiers
11546        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11547            self.write(&u.this.name);
11548        } else {
11549            self.generate_identifier(&u.this)?;
11550        }
11551        Ok(())
11552    }
11553
11554    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11555        self.write_keyword("CACHE");
11556        if c.lazy {
11557            self.write_space();
11558            self.write_keyword("LAZY");
11559        }
11560        self.write_space();
11561        self.write_keyword("TABLE");
11562        self.write_space();
11563        self.generate_identifier(&c.table)?;
11564
11565        // OPTIONS clause
11566        if !c.options.is_empty() {
11567            self.write_space();
11568            self.write_keyword("OPTIONS");
11569            self.write("(");
11570            for (i, (key, value)) in c.options.iter().enumerate() {
11571                if i > 0 {
11572                    self.write(", ");
11573                }
11574                self.generate_expression(key)?;
11575                self.write(" = ");
11576                self.generate_expression(value)?;
11577            }
11578            self.write(")");
11579        }
11580
11581        // AS query
11582        if let Some(query) = &c.query {
11583            self.write_space();
11584            self.write_keyword("AS");
11585            self.write_space();
11586            self.generate_expression(query)?;
11587        }
11588
11589        Ok(())
11590    }
11591
11592    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11593        self.write_keyword("UNCACHE TABLE");
11594        if u.if_exists {
11595            self.write_space();
11596            self.write_keyword("IF EXISTS");
11597        }
11598        self.write_space();
11599        self.generate_identifier(&u.table)?;
11600        Ok(())
11601    }
11602
11603    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11604        self.write_keyword("LOAD DATA");
11605        if l.local {
11606            self.write_space();
11607            self.write_keyword("LOCAL");
11608        }
11609        self.write_space();
11610        self.write_keyword("INPATH");
11611        self.write_space();
11612        self.write("'");
11613        self.write(&l.inpath);
11614        self.write("'");
11615
11616        if l.overwrite {
11617            self.write_space();
11618            self.write_keyword("OVERWRITE");
11619        }
11620
11621        self.write_space();
11622        self.write_keyword("INTO TABLE");
11623        self.write_space();
11624        self.generate_expression(&l.table)?;
11625
11626        // PARTITION clause
11627        if !l.partition.is_empty() {
11628            self.write_space();
11629            self.write_keyword("PARTITION");
11630            self.write("(");
11631            for (i, (col, val)) in l.partition.iter().enumerate() {
11632                if i > 0 {
11633                    self.write(", ");
11634                }
11635                self.generate_identifier(col)?;
11636                self.write(" = ");
11637                self.generate_expression(val)?;
11638            }
11639            self.write(")");
11640        }
11641
11642        // INPUTFORMAT clause
11643        if let Some(fmt) = &l.input_format {
11644            self.write_space();
11645            self.write_keyword("INPUTFORMAT");
11646            self.write_space();
11647            self.write("'");
11648            self.write(fmt);
11649            self.write("'");
11650        }
11651
11652        // SERDE clause
11653        if let Some(serde) = &l.serde {
11654            self.write_space();
11655            self.write_keyword("SERDE");
11656            self.write_space();
11657            self.write("'");
11658            self.write(serde);
11659            self.write("'");
11660        }
11661
11662        Ok(())
11663    }
11664
11665    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11666        self.write_keyword("PRAGMA");
11667        self.write_space();
11668
11669        // Schema prefix if present
11670        if let Some(schema) = &p.schema {
11671            self.generate_identifier(schema)?;
11672            self.write(".");
11673        }
11674
11675        // Pragma name
11676        self.generate_identifier(&p.name)?;
11677
11678        // Value assignment or function call
11679        if p.use_assignment_syntax {
11680            self.write(" = ");
11681            if let Some(value) = &p.value {
11682                self.generate_expression(value)?;
11683            } else if let Some(arg) = p.args.first() {
11684                self.generate_expression(arg)?;
11685            }
11686        } else if !p.args.is_empty() {
11687            self.write("(");
11688            for (i, arg) in p.args.iter().enumerate() {
11689                if i > 0 {
11690                    self.write(", ");
11691                }
11692                self.generate_expression(arg)?;
11693            }
11694            self.write(")");
11695        }
11696
11697        Ok(())
11698    }
11699
11700    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11701        self.write_keyword("GRANT");
11702        self.write_space();
11703
11704        // Privileges (with optional column lists)
11705        for (i, privilege) in g.privileges.iter().enumerate() {
11706            if i > 0 {
11707                self.write(", ");
11708            }
11709            self.write_keyword(&privilege.name);
11710            // Output column list if present: SELECT(col1, col2)
11711            if !privilege.columns.is_empty() {
11712                self.write("(");
11713                for (j, col) in privilege.columns.iter().enumerate() {
11714                    if j > 0 {
11715                        self.write(", ");
11716                    }
11717                    self.write(col);
11718                }
11719                self.write(")");
11720            }
11721        }
11722
11723        self.write_space();
11724        self.write_keyword("ON");
11725        self.write_space();
11726
11727        // Object kind (TABLE, SCHEMA, etc.)
11728        if let Some(kind) = &g.kind {
11729            self.write_keyword(kind);
11730            self.write_space();
11731        }
11732
11733        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11734        {
11735            use crate::dialects::DialectType;
11736            let should_upper = matches!(
11737                self.config.dialect,
11738                Some(DialectType::PostgreSQL)
11739                    | Some(DialectType::CockroachDB)
11740                    | Some(DialectType::Materialize)
11741                    | Some(DialectType::RisingWave)
11742            ) && (g.kind.as_deref() == Some("FUNCTION")
11743                || g.kind.as_deref() == Some("PROCEDURE"));
11744            if should_upper {
11745                use crate::expressions::Identifier;
11746                let upper_id = Identifier {
11747                    name: g.securable.name.to_ascii_uppercase(),
11748                    quoted: g.securable.quoted,
11749                    ..g.securable.clone()
11750                };
11751                self.generate_identifier(&upper_id)?;
11752            } else {
11753                self.generate_identifier(&g.securable)?;
11754            }
11755        }
11756
11757        // Function parameter types (if present)
11758        if !g.function_params.is_empty() {
11759            self.write("(");
11760            for (i, param) in g.function_params.iter().enumerate() {
11761                if i > 0 {
11762                    self.write(", ");
11763                }
11764                self.write(param);
11765            }
11766            self.write(")");
11767        }
11768
11769        self.write_space();
11770        self.write_keyword("TO");
11771        self.write_space();
11772
11773        // Principals
11774        for (i, principal) in g.principals.iter().enumerate() {
11775            if i > 0 {
11776                self.write(", ");
11777            }
11778            if principal.is_role {
11779                self.write_keyword("ROLE");
11780                self.write_space();
11781            } else if principal.is_group {
11782                self.write_keyword("GROUP");
11783                self.write_space();
11784            } else if principal.is_share {
11785                self.write_keyword("SHARE");
11786                self.write_space();
11787            }
11788            self.generate_identifier(&principal.name)?;
11789        }
11790
11791        // WITH GRANT OPTION
11792        if g.grant_option {
11793            self.write_space();
11794            self.write_keyword("WITH GRANT OPTION");
11795        }
11796
11797        // TSQL: AS principal
11798        if let Some(ref principal) = g.as_principal {
11799            self.write_space();
11800            self.write_keyword("AS");
11801            self.write_space();
11802            self.generate_identifier(principal)?;
11803        }
11804
11805        Ok(())
11806    }
11807
11808    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11809        self.write_keyword("REVOKE");
11810        self.write_space();
11811
11812        // GRANT OPTION FOR
11813        if r.grant_option {
11814            self.write_keyword("GRANT OPTION FOR");
11815            self.write_space();
11816        }
11817
11818        // Privileges (with optional column lists)
11819        for (i, privilege) in r.privileges.iter().enumerate() {
11820            if i > 0 {
11821                self.write(", ");
11822            }
11823            self.write_keyword(&privilege.name);
11824            // Output column list if present: SELECT(col1, col2)
11825            if !privilege.columns.is_empty() {
11826                self.write("(");
11827                for (j, col) in privilege.columns.iter().enumerate() {
11828                    if j > 0 {
11829                        self.write(", ");
11830                    }
11831                    self.write(col);
11832                }
11833                self.write(")");
11834            }
11835        }
11836
11837        self.write_space();
11838        self.write_keyword("ON");
11839        self.write_space();
11840
11841        // Object kind
11842        if let Some(kind) = &r.kind {
11843            self.write_keyword(kind);
11844            self.write_space();
11845        }
11846
11847        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11848        {
11849            use crate::dialects::DialectType;
11850            let should_upper = matches!(
11851                self.config.dialect,
11852                Some(DialectType::PostgreSQL)
11853                    | Some(DialectType::CockroachDB)
11854                    | Some(DialectType::Materialize)
11855                    | Some(DialectType::RisingWave)
11856            ) && (r.kind.as_deref() == Some("FUNCTION")
11857                || r.kind.as_deref() == Some("PROCEDURE"));
11858            if should_upper {
11859                use crate::expressions::Identifier;
11860                let upper_id = Identifier {
11861                    name: r.securable.name.to_ascii_uppercase(),
11862                    quoted: r.securable.quoted,
11863                    ..r.securable.clone()
11864                };
11865                self.generate_identifier(&upper_id)?;
11866            } else {
11867                self.generate_identifier(&r.securable)?;
11868            }
11869        }
11870
11871        // Function parameter types (if present)
11872        if !r.function_params.is_empty() {
11873            self.write("(");
11874            for (i, param) in r.function_params.iter().enumerate() {
11875                if i > 0 {
11876                    self.write(", ");
11877                }
11878                self.write(param);
11879            }
11880            self.write(")");
11881        }
11882
11883        self.write_space();
11884        self.write_keyword("FROM");
11885        self.write_space();
11886
11887        // Principals
11888        for (i, principal) in r.principals.iter().enumerate() {
11889            if i > 0 {
11890                self.write(", ");
11891            }
11892            if principal.is_role {
11893                self.write_keyword("ROLE");
11894                self.write_space();
11895            } else if principal.is_group {
11896                self.write_keyword("GROUP");
11897                self.write_space();
11898            } else if principal.is_share {
11899                self.write_keyword("SHARE");
11900                self.write_space();
11901            }
11902            self.generate_identifier(&principal.name)?;
11903        }
11904
11905        // CASCADE or RESTRICT
11906        if r.cascade {
11907            self.write_space();
11908            self.write_keyword("CASCADE");
11909        } else if r.restrict {
11910            self.write_space();
11911            self.write_keyword("RESTRICT");
11912        }
11913
11914        Ok(())
11915    }
11916
11917    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11918        self.write_keyword("COMMENT");
11919
11920        // IF EXISTS
11921        if c.exists {
11922            self.write_space();
11923            self.write_keyword("IF EXISTS");
11924        }
11925
11926        self.write_space();
11927        self.write_keyword("ON");
11928
11929        // MATERIALIZED
11930        if c.materialized {
11931            self.write_space();
11932            self.write_keyword("MATERIALIZED");
11933        }
11934
11935        self.write_space();
11936        self.write_keyword(&c.kind);
11937        self.write_space();
11938
11939        // Object name
11940        self.generate_expression(&c.this)?;
11941
11942        self.write_space();
11943        self.write_keyword("IS");
11944        self.write_space();
11945
11946        // Comment expression
11947        self.generate_expression(&c.expression)?;
11948
11949        Ok(())
11950    }
11951
11952    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11953        self.write_keyword("SET");
11954
11955        for (i, item) in s.items.iter().enumerate() {
11956            if i > 0 {
11957                self.write(",");
11958            }
11959            self.write_space();
11960
11961            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11962            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11963            if let Some(ref kind) = item.kind {
11964                // For VARIABLE kind, only output the keyword for dialects that require it
11965                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11966                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11967                if has_variable_kind {
11968                    if matches!(
11969                        self.config.dialect,
11970                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11971                    ) {
11972                        self.write_keyword("VARIABLE");
11973                        self.write_space();
11974                    }
11975                } else {
11976                    self.write_keyword(kind);
11977                    self.write_space();
11978                }
11979            }
11980
11981            // Check for special SET forms by name
11982            let name_str = match &item.name {
11983                Expression::Identifier(id) => Some(id.name.as_str()),
11984                _ => None,
11985            };
11986
11987            let is_transaction = name_str == Some("TRANSACTION");
11988            let is_character_set = name_str == Some("CHARACTER SET");
11989            let is_names = name_str == Some("NAMES");
11990            let is_collate = name_str == Some("COLLATE");
11991            let is_value_only =
11992                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11993
11994            if is_transaction {
11995                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11996                self.write_keyword("TRANSACTION");
11997                if let Expression::Identifier(id) = &item.value {
11998                    if !id.name.is_empty() {
11999                        self.write_space();
12000                        self.write(&id.name);
12001                    }
12002                }
12003            } else if is_character_set {
12004                // Output: SET CHARACTER SET <charset>
12005                self.write_keyword("CHARACTER SET");
12006                self.write_space();
12007                self.generate_set_value(&item.value)?;
12008            } else if is_names {
12009                // Output: SET NAMES <charset>
12010                self.write_keyword("NAMES");
12011                self.write_space();
12012                self.generate_set_value(&item.value)?;
12013            } else if is_collate {
12014                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12015                self.write_keyword("COLLATE");
12016                self.write_space();
12017                self.generate_set_value(&item.value)?;
12018            } else if has_variable_kind {
12019                // Output: SET [VARIABLE] <name> = <value>
12020                // VARIABLE keyword already written above if dialect requires it
12021                if let Some(ns) = name_str {
12022                    self.write(ns);
12023                } else {
12024                    self.generate_expression(&item.name)?;
12025                }
12026                self.write(" = ");
12027                self.generate_set_value(&item.value)?;
12028            } else if is_value_only {
12029                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12030                self.generate_expression(&item.name)?;
12031            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12032                // SET key value without = (TSQL style)
12033                self.generate_expression(&item.name)?;
12034                self.write_space();
12035                self.generate_set_value(&item.value)?;
12036            } else {
12037                // Standard: variable = value
12038                // SET item names should not be quoted (they are config parameter names, not column refs)
12039                match &item.name {
12040                    Expression::Identifier(id) => {
12041                        self.write(&id.name);
12042                    }
12043                    _ => {
12044                        self.generate_expression(&item.name)?;
12045                    }
12046                }
12047                self.write(" = ");
12048                self.generate_set_value(&item.value)?;
12049            }
12050        }
12051
12052        Ok(())
12053    }
12054
12055    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12056    /// directly to avoid reserved keyword quoting.
12057    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12058        if let Expression::Identifier(id) = value {
12059            match id.name.as_str() {
12060                "DEFAULT" | "ON" | "OFF" => {
12061                    self.write_keyword(&id.name);
12062                    return Ok(());
12063                }
12064                _ => {}
12065            }
12066        }
12067        self.generate_expression(value)
12068    }
12069
12070    // ==================== Phase 4: Additional DDL Generation ====================
12071
12072    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12073        self.write_keyword("ALTER");
12074        // MySQL modifiers before VIEW
12075        if let Some(ref algorithm) = av.algorithm {
12076            self.write_space();
12077            self.write_keyword("ALGORITHM");
12078            self.write(" = ");
12079            self.write_keyword(algorithm);
12080        }
12081        if let Some(ref definer) = av.definer {
12082            self.write_space();
12083            self.write_keyword("DEFINER");
12084            self.write(" = ");
12085            self.write(definer);
12086        }
12087        if let Some(ref sql_security) = av.sql_security {
12088            self.write_space();
12089            self.write_keyword("SQL SECURITY");
12090            self.write(" = ");
12091            self.write_keyword(sql_security);
12092        }
12093        self.write_space();
12094        self.write_keyword("VIEW");
12095        self.write_space();
12096        self.generate_table(&av.name)?;
12097
12098        // Hive: Column aliases with optional COMMENT
12099        if !av.columns.is_empty() {
12100            self.write(" (");
12101            for (i, col) in av.columns.iter().enumerate() {
12102                if i > 0 {
12103                    self.write(", ");
12104                }
12105                self.generate_identifier(&col.name)?;
12106                if let Some(ref comment) = col.comment {
12107                    self.write_space();
12108                    self.write_keyword("COMMENT");
12109                    self.write(" ");
12110                    self.generate_string_literal(comment)?;
12111                }
12112            }
12113            self.write(")");
12114        }
12115
12116        // TSQL: WITH option before actions
12117        if let Some(ref opt) = av.with_option {
12118            self.write_space();
12119            self.write_keyword("WITH");
12120            self.write_space();
12121            self.write_keyword(opt);
12122        }
12123
12124        for action in &av.actions {
12125            self.write_space();
12126            match action {
12127                AlterViewAction::Rename(new_name) => {
12128                    self.write_keyword("RENAME TO");
12129                    self.write_space();
12130                    self.generate_table(new_name)?;
12131                }
12132                AlterViewAction::OwnerTo(owner) => {
12133                    self.write_keyword("OWNER TO");
12134                    self.write_space();
12135                    self.generate_identifier(owner)?;
12136                }
12137                AlterViewAction::SetSchema(schema) => {
12138                    self.write_keyword("SET SCHEMA");
12139                    self.write_space();
12140                    self.generate_identifier(schema)?;
12141                }
12142                AlterViewAction::SetAuthorization(auth) => {
12143                    self.write_keyword("SET AUTHORIZATION");
12144                    self.write_space();
12145                    self.write(auth);
12146                }
12147                AlterViewAction::AlterColumn { name, action } => {
12148                    self.write_keyword("ALTER COLUMN");
12149                    self.write_space();
12150                    self.generate_identifier(name)?;
12151                    self.write_space();
12152                    self.generate_alter_column_action(action)?;
12153                }
12154                AlterViewAction::AsSelect(query) => {
12155                    self.write_keyword("AS");
12156                    self.write_space();
12157                    self.generate_expression(query)?;
12158                }
12159                AlterViewAction::SetTblproperties(props) => {
12160                    self.write_keyword("SET TBLPROPERTIES");
12161                    self.write(" (");
12162                    for (i, (key, value)) in props.iter().enumerate() {
12163                        if i > 0 {
12164                            self.write(", ");
12165                        }
12166                        self.generate_string_literal(key)?;
12167                        self.write("=");
12168                        self.generate_string_literal(value)?;
12169                    }
12170                    self.write(")");
12171                }
12172                AlterViewAction::UnsetTblproperties(keys) => {
12173                    self.write_keyword("UNSET TBLPROPERTIES");
12174                    self.write(" (");
12175                    for (i, key) in keys.iter().enumerate() {
12176                        if i > 0 {
12177                            self.write(", ");
12178                        }
12179                        self.generate_string_literal(key)?;
12180                    }
12181                    self.write(")");
12182                }
12183            }
12184        }
12185
12186        Ok(())
12187    }
12188
12189    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12190        self.write_keyword("ALTER INDEX");
12191        self.write_space();
12192        self.generate_identifier(&ai.name)?;
12193
12194        if let Some(table) = &ai.table {
12195            self.write_space();
12196            self.write_keyword("ON");
12197            self.write_space();
12198            self.generate_table(table)?;
12199        }
12200
12201        for action in &ai.actions {
12202            self.write_space();
12203            match action {
12204                AlterIndexAction::Rename(new_name) => {
12205                    self.write_keyword("RENAME TO");
12206                    self.write_space();
12207                    self.generate_identifier(new_name)?;
12208                }
12209                AlterIndexAction::SetTablespace(tablespace) => {
12210                    self.write_keyword("SET TABLESPACE");
12211                    self.write_space();
12212                    self.generate_identifier(tablespace)?;
12213                }
12214                AlterIndexAction::Visible(visible) => {
12215                    if *visible {
12216                        self.write_keyword("VISIBLE");
12217                    } else {
12218                        self.write_keyword("INVISIBLE");
12219                    }
12220                }
12221            }
12222        }
12223
12224        Ok(())
12225    }
12226
12227    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12228        // Output leading comments
12229        for comment in &cs.leading_comments {
12230            self.write_formatted_comment(comment);
12231            self.write_space();
12232        }
12233
12234        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12235        let saved_athena_hive_context = self.athena_hive_context;
12236        if matches!(
12237            self.config.dialect,
12238            Some(crate::dialects::DialectType::Athena)
12239        ) {
12240            self.athena_hive_context = true;
12241        }
12242
12243        self.write_keyword("CREATE SCHEMA");
12244
12245        if cs.if_not_exists {
12246            self.write_space();
12247            self.write_keyword("IF NOT EXISTS");
12248        }
12249
12250        self.write_space();
12251        for (i, part) in cs.name.iter().enumerate() {
12252            if i > 0 {
12253                self.write(".");
12254            }
12255            self.generate_identifier(part)?;
12256        }
12257
12258        if let Some(ref clone_parts) = cs.clone_from {
12259            self.write_keyword(" CLONE ");
12260            for (i, part) in clone_parts.iter().enumerate() {
12261                if i > 0 {
12262                    self.write(".");
12263                }
12264                self.generate_identifier(part)?;
12265            }
12266        }
12267
12268        if let Some(ref at_clause) = cs.at_clause {
12269            self.write_space();
12270            self.generate_expression(at_clause)?;
12271        }
12272
12273        if let Some(auth) = &cs.authorization {
12274            self.write_space();
12275            self.write_keyword("AUTHORIZATION");
12276            self.write_space();
12277            self.generate_identifier(auth)?;
12278        }
12279
12280        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12281        // Separate WITH properties from other properties
12282        let with_properties: Vec<_> = cs
12283            .properties
12284            .iter()
12285            .filter(|p| matches!(p, Expression::Property(_)))
12286            .collect();
12287        let other_properties: Vec<_> = cs
12288            .properties
12289            .iter()
12290            .filter(|p| !matches!(p, Expression::Property(_)))
12291            .collect();
12292
12293        // Generate WITH (props) if we have Property expressions
12294        if !with_properties.is_empty() {
12295            self.write_space();
12296            self.write_keyword("WITH");
12297            self.write(" (");
12298            for (i, prop) in with_properties.iter().enumerate() {
12299                if i > 0 {
12300                    self.write(", ");
12301                }
12302                self.generate_expression(prop)?;
12303            }
12304            self.write(")");
12305        }
12306
12307        // Generate other properties (like DEFAULT COLLATE)
12308        for prop in other_properties {
12309            self.write_space();
12310            self.generate_expression(prop)?;
12311        }
12312
12313        // Restore Athena Hive context
12314        self.athena_hive_context = saved_athena_hive_context;
12315
12316        Ok(())
12317    }
12318
12319    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12320        self.write_keyword("DROP SCHEMA");
12321
12322        if ds.if_exists {
12323            self.write_space();
12324            self.write_keyword("IF EXISTS");
12325        }
12326
12327        self.write_space();
12328        self.generate_identifier(&ds.name)?;
12329
12330        if ds.cascade {
12331            self.write_space();
12332            self.write_keyword("CASCADE");
12333        }
12334
12335        Ok(())
12336    }
12337
12338    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12339        self.write_keyword("DROP NAMESPACE");
12340
12341        if dn.if_exists {
12342            self.write_space();
12343            self.write_keyword("IF EXISTS");
12344        }
12345
12346        self.write_space();
12347        self.generate_identifier(&dn.name)?;
12348
12349        if dn.cascade {
12350            self.write_space();
12351            self.write_keyword("CASCADE");
12352        }
12353
12354        Ok(())
12355    }
12356
12357    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12358        self.write_keyword("CREATE DATABASE");
12359
12360        if cd.if_not_exists {
12361            self.write_space();
12362            self.write_keyword("IF NOT EXISTS");
12363        }
12364
12365        self.write_space();
12366        self.generate_identifier(&cd.name)?;
12367
12368        if let Some(ref clone_src) = cd.clone_from {
12369            self.write_keyword(" CLONE ");
12370            self.generate_identifier(clone_src)?;
12371        }
12372
12373        // AT/BEFORE clause for time travel (Snowflake)
12374        if let Some(ref at_clause) = cd.at_clause {
12375            self.write_space();
12376            self.generate_expression(at_clause)?;
12377        }
12378
12379        for option in &cd.options {
12380            self.write_space();
12381            match option {
12382                DatabaseOption::CharacterSet(charset) => {
12383                    self.write_keyword("CHARACTER SET");
12384                    self.write(" = ");
12385                    self.write(&format!("'{}'", charset));
12386                }
12387                DatabaseOption::Collate(collate) => {
12388                    self.write_keyword("COLLATE");
12389                    self.write(" = ");
12390                    self.write(&format!("'{}'", collate));
12391                }
12392                DatabaseOption::Owner(owner) => {
12393                    self.write_keyword("OWNER");
12394                    self.write(" = ");
12395                    self.generate_identifier(owner)?;
12396                }
12397                DatabaseOption::Template(template) => {
12398                    self.write_keyword("TEMPLATE");
12399                    self.write(" = ");
12400                    self.generate_identifier(template)?;
12401                }
12402                DatabaseOption::Encoding(encoding) => {
12403                    self.write_keyword("ENCODING");
12404                    self.write(" = ");
12405                    self.write(&format!("'{}'", encoding));
12406                }
12407                DatabaseOption::Location(location) => {
12408                    self.write_keyword("LOCATION");
12409                    self.write(" = ");
12410                    self.write(&format!("'{}'", location));
12411                }
12412            }
12413        }
12414
12415        Ok(())
12416    }
12417
12418    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12419        self.write_keyword("DROP DATABASE");
12420
12421        if dd.if_exists {
12422            self.write_space();
12423            self.write_keyword("IF EXISTS");
12424        }
12425
12426        self.write_space();
12427        self.generate_identifier(&dd.name)?;
12428
12429        if dd.sync {
12430            self.write_space();
12431            self.write_keyword("SYNC");
12432        }
12433
12434        Ok(())
12435    }
12436
12437    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12438        self.write_keyword("CREATE");
12439
12440        if cf.or_alter {
12441            self.write_space();
12442            self.write_keyword("OR ALTER");
12443        } else if cf.or_replace {
12444            self.write_space();
12445            self.write_keyword("OR REPLACE");
12446        }
12447
12448        if cf.temporary {
12449            self.write_space();
12450            self.write_keyword("TEMPORARY");
12451        }
12452
12453        self.write_space();
12454        if cf.is_table_function {
12455            self.write_keyword("TABLE FUNCTION");
12456        } else {
12457            self.write_keyword("FUNCTION");
12458        }
12459
12460        if cf.if_not_exists {
12461            self.write_space();
12462            self.write_keyword("IF NOT EXISTS");
12463        }
12464
12465        self.write_space();
12466        self.generate_table(&cf.name)?;
12467        if cf.has_parens {
12468            let func_multiline = self.config.pretty
12469                && matches!(
12470                    self.config.dialect,
12471                    Some(crate::dialects::DialectType::TSQL)
12472                        | Some(crate::dialects::DialectType::Fabric)
12473                )
12474                && !cf.parameters.is_empty();
12475            if func_multiline {
12476                self.write("(\n");
12477                self.indent_level += 2;
12478                self.write_indent();
12479                self.generate_function_parameters(&cf.parameters)?;
12480                self.write("\n");
12481                self.indent_level -= 2;
12482                self.write(")");
12483            } else {
12484                self.write("(");
12485                self.generate_function_parameters(&cf.parameters)?;
12486                self.write(")");
12487            }
12488        }
12489
12490        // Output RETURNS clause (always comes first after parameters)
12491        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12492        let use_multiline = self.config.pretty
12493            && matches!(
12494                self.config.dialect,
12495                Some(crate::dialects::DialectType::BigQuery)
12496                    | Some(crate::dialects::DialectType::TSQL)
12497                    | Some(crate::dialects::DialectType::Fabric)
12498            );
12499
12500        if cf.language_first {
12501            // LANGUAGE first, then SQL data access, then RETURNS
12502            if let Some(lang) = &cf.language {
12503                if use_multiline {
12504                    self.write_newline();
12505                } else {
12506                    self.write_space();
12507                }
12508                self.write_keyword("LANGUAGE");
12509                self.write_space();
12510                self.write(lang);
12511            }
12512
12513            // SQL data access comes after LANGUAGE in this case
12514            if let Some(sql_data) = &cf.sql_data_access {
12515                self.write_space();
12516                match sql_data {
12517                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12518                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12519                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12520                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12521                }
12522            }
12523
12524            if let Some(ref rtb) = cf.returns_table_body {
12525                if use_multiline {
12526                    self.write_newline();
12527                } else {
12528                    self.write_space();
12529                }
12530                self.write_keyword("RETURNS");
12531                self.write_space();
12532                self.write(rtb);
12533            } else if let Some(return_type) = &cf.return_type {
12534                if use_multiline {
12535                    self.write_newline();
12536                } else {
12537                    self.write_space();
12538                }
12539                self.write_keyword("RETURNS");
12540                self.write_space();
12541                self.generate_data_type(return_type)?;
12542            }
12543        } else {
12544            // RETURNS first (default)
12545            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12546            let is_duckdb = matches!(
12547                self.config.dialect,
12548                Some(crate::dialects::DialectType::DuckDB)
12549            );
12550            if let Some(ref rtb) = cf.returns_table_body {
12551                if !(is_duckdb && rtb.is_empty()) {
12552                    if use_multiline {
12553                        self.write_newline();
12554                    } else {
12555                        self.write_space();
12556                    }
12557                    self.write_keyword("RETURNS");
12558                    self.write_space();
12559                    self.write(rtb);
12560                }
12561            } else if let Some(return_type) = &cf.return_type {
12562                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12563                if !is_duckdb {
12564                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12565                    if use_multiline {
12566                        self.write_newline();
12567                    } else {
12568                        self.write_space();
12569                    }
12570                    self.write_keyword("RETURNS");
12571                    self.write_space();
12572                    if is_table_return {
12573                        self.write_keyword("TABLE");
12574                    } else {
12575                        self.generate_data_type(return_type)?;
12576                    }
12577                }
12578            }
12579        }
12580
12581        // If we have property_order, use it to output properties in original order
12582        if !cf.property_order.is_empty() {
12583            // For BigQuery, OPTIONS must come before AS - reorder if needed
12584            let is_bigquery = matches!(
12585                self.config.dialect,
12586                Some(crate::dialects::DialectType::BigQuery)
12587            );
12588            let property_order = if is_bigquery {
12589                // Move Options before As if both are present
12590                let mut reordered = Vec::new();
12591                let mut has_as = false;
12592                let mut has_options = false;
12593                for prop in &cf.property_order {
12594                    match prop {
12595                        FunctionPropertyKind::As => has_as = true,
12596                        FunctionPropertyKind::Options => has_options = true,
12597                        _ => {}
12598                    }
12599                }
12600                if has_as && has_options {
12601                    // Output all props except As and Options, then Options, then As
12602                    for prop in &cf.property_order {
12603                        if *prop != FunctionPropertyKind::As
12604                            && *prop != FunctionPropertyKind::Options
12605                        {
12606                            reordered.push(*prop);
12607                        }
12608                    }
12609                    reordered.push(FunctionPropertyKind::Options);
12610                    reordered.push(FunctionPropertyKind::As);
12611                    reordered
12612                } else {
12613                    cf.property_order.clone()
12614                }
12615            } else {
12616                cf.property_order.clone()
12617            };
12618
12619            for prop in &property_order {
12620                match prop {
12621                    FunctionPropertyKind::Set => {
12622                        self.generate_function_set_options(cf)?;
12623                    }
12624                    FunctionPropertyKind::As => {
12625                        self.generate_function_body(cf)?;
12626                    }
12627                    FunctionPropertyKind::Using => {
12628                        self.generate_function_using_resources(cf)?;
12629                    }
12630                    FunctionPropertyKind::Language => {
12631                        if !cf.language_first {
12632                            // Only output here if not already output above
12633                            if let Some(lang) = &cf.language {
12634                                // Only BigQuery uses multiline formatting
12635                                let use_multiline = self.config.pretty
12636                                    && matches!(
12637                                        self.config.dialect,
12638                                        Some(crate::dialects::DialectType::BigQuery)
12639                                    );
12640                                if use_multiline {
12641                                    self.write_newline();
12642                                } else {
12643                                    self.write_space();
12644                                }
12645                                self.write_keyword("LANGUAGE");
12646                                self.write_space();
12647                                self.write(lang);
12648                            }
12649                        }
12650                    }
12651                    FunctionPropertyKind::Determinism => {
12652                        self.generate_function_determinism(cf)?;
12653                    }
12654                    FunctionPropertyKind::NullInput => {
12655                        self.generate_function_null_input(cf)?;
12656                    }
12657                    FunctionPropertyKind::Security => {
12658                        self.generate_function_security(cf)?;
12659                    }
12660                    FunctionPropertyKind::SqlDataAccess => {
12661                        if !cf.language_first {
12662                            // Only output here if not already output above
12663                            self.generate_function_sql_data_access(cf)?;
12664                        }
12665                    }
12666                    FunctionPropertyKind::Options => {
12667                        if !cf.options.is_empty() {
12668                            self.write_space();
12669                            self.generate_options_clause(&cf.options)?;
12670                        }
12671                    }
12672                    FunctionPropertyKind::Environment => {
12673                        if !cf.environment.is_empty() {
12674                            self.write_space();
12675                            self.generate_environment_clause(&cf.environment)?;
12676                        }
12677                    }
12678                    FunctionPropertyKind::Handler => {
12679                        if let Some(ref h) = cf.handler {
12680                            self.write_space();
12681                            self.write_keyword("HANDLER");
12682                            if cf.handler_uses_eq {
12683                                self.write(" = ");
12684                            } else {
12685                                self.write_space();
12686                            }
12687                            self.write("'");
12688                            self.write(h);
12689                            self.write("'");
12690                        }
12691                    }
12692                    FunctionPropertyKind::RuntimeVersion => {
12693                        if let Some(ref runtime_version) = cf.runtime_version {
12694                            self.write_space();
12695                            self.write_keyword("RUNTIME_VERSION");
12696                            self.write("='");
12697                            self.write(runtime_version);
12698                            self.write("'");
12699                        }
12700                    }
12701                    FunctionPropertyKind::Packages => {
12702                        if let Some(ref packages) = cf.packages {
12703                            self.write_space();
12704                            self.write_keyword("PACKAGES");
12705                            self.write("=(");
12706                            for (i, package) in packages.iter().enumerate() {
12707                                if i > 0 {
12708                                    self.write(", ");
12709                                }
12710                                self.write("'");
12711                                self.write(package);
12712                                self.write("'");
12713                            }
12714                            self.write(")");
12715                        }
12716                    }
12717                    FunctionPropertyKind::ParameterStyle => {
12718                        if let Some(ref ps) = cf.parameter_style {
12719                            self.write_space();
12720                            self.write_keyword("PARAMETER STYLE");
12721                            self.write_space();
12722                            self.write_keyword(ps);
12723                        }
12724                    }
12725                }
12726            }
12727
12728            // Output OPTIONS if not tracked in property_order (legacy)
12729            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12730            {
12731                self.write_space();
12732                self.generate_options_clause(&cf.options)?;
12733            }
12734
12735            // Output ENVIRONMENT if not tracked in property_order (legacy)
12736            if !cf.environment.is_empty()
12737                && !cf
12738                    .property_order
12739                    .contains(&FunctionPropertyKind::Environment)
12740            {
12741                self.write_space();
12742                self.generate_environment_clause(&cf.environment)?;
12743            }
12744        } else {
12745            // Legacy behavior when property_order is empty
12746            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12747            if matches!(
12748                self.config.dialect,
12749                Some(crate::dialects::DialectType::BigQuery)
12750            ) {
12751                self.generate_function_determinism(cf)?;
12752            }
12753
12754            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12755            let use_multiline = self.config.pretty
12756                && matches!(
12757                    self.config.dialect,
12758                    Some(crate::dialects::DialectType::BigQuery)
12759                );
12760
12761            if !cf.language_first {
12762                if let Some(lang) = &cf.language {
12763                    if use_multiline {
12764                        self.write_newline();
12765                    } else {
12766                        self.write_space();
12767                    }
12768                    self.write_keyword("LANGUAGE");
12769                    self.write_space();
12770                    self.write(lang);
12771                }
12772
12773                // SQL data access characteristic comes after LANGUAGE
12774                self.generate_function_sql_data_access(cf)?;
12775            }
12776
12777            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12778            if !matches!(
12779                self.config.dialect,
12780                Some(crate::dialects::DialectType::BigQuery)
12781            ) {
12782                self.generate_function_determinism(cf)?;
12783            }
12784
12785            self.generate_function_null_input(cf)?;
12786            self.generate_function_security(cf)?;
12787            self.generate_function_set_options(cf)?;
12788
12789            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12790            if !cf.options.is_empty() {
12791                self.write_space();
12792                self.generate_options_clause(&cf.options)?;
12793            }
12794
12795            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12796            if !cf.environment.is_empty() {
12797                self.write_space();
12798                self.generate_environment_clause(&cf.environment)?;
12799            }
12800
12801            if let Some(ref h) = cf.handler {
12802                self.write_space();
12803                self.write_keyword("HANDLER");
12804                if cf.handler_uses_eq {
12805                    self.write(" = ");
12806                } else {
12807                    self.write_space();
12808                }
12809                self.write("'");
12810                self.write(h);
12811                self.write("'");
12812            }
12813
12814            if let Some(ref runtime_version) = cf.runtime_version {
12815                self.write_space();
12816                self.write_keyword("RUNTIME_VERSION");
12817                self.write("='");
12818                self.write(runtime_version);
12819                self.write("'");
12820            }
12821
12822            if let Some(ref packages) = cf.packages {
12823                self.write_space();
12824                self.write_keyword("PACKAGES");
12825                self.write("=(");
12826                for (i, package) in packages.iter().enumerate() {
12827                    if i > 0 {
12828                        self.write(", ");
12829                    }
12830                    self.write("'");
12831                    self.write(package);
12832                    self.write("'");
12833                }
12834                self.write(")");
12835            }
12836
12837            self.generate_function_body(cf)?;
12838            self.generate_function_using_resources(cf)?;
12839        }
12840
12841        Ok(())
12842    }
12843
12844    /// Generate SET options for CREATE FUNCTION
12845    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12846        for opt in &cf.set_options {
12847            self.write_space();
12848            self.write_keyword("SET");
12849            self.write_space();
12850            self.write(&opt.name);
12851            match &opt.value {
12852                FunctionSetValue::Value { value, use_to } => {
12853                    if *use_to {
12854                        self.write(" TO ");
12855                    } else {
12856                        self.write(" = ");
12857                    }
12858                    self.write(value);
12859                }
12860                FunctionSetValue::FromCurrent => {
12861                    self.write_space();
12862                    self.write_keyword("FROM CURRENT");
12863                }
12864            }
12865        }
12866        Ok(())
12867    }
12868
12869    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
12870        if cf.using_resources.is_empty() {
12871            return Ok(());
12872        }
12873
12874        self.write_space();
12875        self.write_keyword("USING");
12876        for resource in &cf.using_resources {
12877            self.write_space();
12878            self.write_keyword(&resource.kind);
12879            self.write_space();
12880            self.generate_string_literal(&resource.uri)?;
12881        }
12882        Ok(())
12883    }
12884
12885    /// Generate function body (AS clause)
12886    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12887        if let Some(body) = &cf.body {
12888            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12889            self.write_space();
12890            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12891            let use_multiline = self.config.pretty
12892                && matches!(
12893                    self.config.dialect,
12894                    Some(crate::dialects::DialectType::BigQuery)
12895                );
12896            match body {
12897                FunctionBody::Block(block) => {
12898                    self.write_keyword("AS");
12899                    if matches!(
12900                        self.config.dialect,
12901                        Some(crate::dialects::DialectType::TSQL)
12902                    ) {
12903                        self.write(" BEGIN ");
12904                        self.write(block);
12905                        self.write(" END");
12906                    } else if matches!(
12907                        self.config.dialect,
12908                        Some(crate::dialects::DialectType::PostgreSQL)
12909                    ) {
12910                        self.write(" $$");
12911                        self.write(block);
12912                        self.write("$$");
12913                    } else {
12914                        // Escape content for single-quoted output
12915                        let escaped = self.escape_block_for_single_quote(block);
12916                        // In BigQuery pretty mode, body content goes on new line
12917                        if use_multiline {
12918                            self.write_newline();
12919                        } else {
12920                            self.write(" ");
12921                        }
12922                        self.write("'");
12923                        self.write(&escaped);
12924                        self.write("'");
12925                    }
12926                }
12927                FunctionBody::StringLiteral(s) => {
12928                    self.write_keyword("AS");
12929                    // In BigQuery pretty mode, body content goes on new line
12930                    if use_multiline {
12931                        self.write_newline();
12932                    } else {
12933                        self.write(" ");
12934                    }
12935                    self.write("'");
12936                    self.write(s);
12937                    self.write("'");
12938                }
12939                FunctionBody::Expression(expr) => {
12940                    self.write_keyword("AS");
12941                    self.write_space();
12942                    self.generate_expression(expr)?;
12943                }
12944                FunctionBody::External(name) => {
12945                    self.write_keyword("EXTERNAL NAME");
12946                    self.write(" '");
12947                    self.write(name);
12948                    self.write("'");
12949                }
12950                FunctionBody::Return(expr) => {
12951                    if matches!(
12952                        self.config.dialect,
12953                        Some(crate::dialects::DialectType::DuckDB)
12954                    ) {
12955                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12956                        self.write_keyword("AS");
12957                        self.write_space();
12958                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12959                        let is_table_return = cf.returns_table_body.is_some()
12960                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12961                        if is_table_return {
12962                            self.write_keyword("TABLE");
12963                            self.write_space();
12964                        }
12965                        self.generate_expression(expr)?;
12966                    } else {
12967                        if self.config.create_function_return_as {
12968                            self.write_keyword("AS");
12969                            // TSQL pretty: newline between AS and RETURN
12970                            if self.config.pretty
12971                                && matches!(
12972                                    self.config.dialect,
12973                                    Some(crate::dialects::DialectType::TSQL)
12974                                        | Some(crate::dialects::DialectType::Fabric)
12975                                )
12976                            {
12977                                self.write_newline();
12978                            } else {
12979                                self.write_space();
12980                            }
12981                        }
12982                        self.write_keyword("RETURN");
12983                        self.write_space();
12984                        self.generate_expression(expr)?;
12985                    }
12986                }
12987                FunctionBody::Statements(stmts) => {
12988                    self.write_keyword("AS");
12989                    self.write(" BEGIN ");
12990                    for (i, stmt) in stmts.iter().enumerate() {
12991                        if i > 0 {
12992                            self.write(" ");
12993                        }
12994                        self.generate_expression(stmt)?;
12995                        self.write(";");
12996                    }
12997                    self.write(" END");
12998                }
12999                FunctionBody::RawBlock(text) => {
13000                    self.write_newline();
13001                    self.write(text);
13002                }
13003                FunctionBody::DollarQuoted { content, tag } => {
13004                    self.write_keyword("AS");
13005                    self.write(" ");
13006                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13007                    let supports_dollar_quoting = matches!(
13008                        self.config.dialect,
13009                        Some(crate::dialects::DialectType::PostgreSQL)
13010                            | Some(crate::dialects::DialectType::Databricks)
13011                            | Some(crate::dialects::DialectType::Redshift)
13012                            | Some(crate::dialects::DialectType::DuckDB)
13013                    );
13014                    if supports_dollar_quoting {
13015                        // Output in dollar-quoted format
13016                        self.write("$");
13017                        if let Some(t) = tag {
13018                            self.write(t);
13019                        }
13020                        self.write("$");
13021                        self.write(content);
13022                        self.write("$");
13023                        if let Some(t) = tag {
13024                            self.write(t);
13025                        }
13026                        self.write("$");
13027                    } else {
13028                        // Convert to single-quoted string for other dialects
13029                        let escaped = self.escape_block_for_single_quote(content);
13030                        self.write("'");
13031                        self.write(&escaped);
13032                        self.write("'");
13033                    }
13034                }
13035            }
13036        }
13037        Ok(())
13038    }
13039
13040    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13041    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13042        if let Some(det) = cf.deterministic {
13043            self.write_space();
13044            if matches!(
13045                self.config.dialect,
13046                Some(crate::dialects::DialectType::BigQuery)
13047            ) {
13048                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13049                if det {
13050                    self.write_keyword("DETERMINISTIC");
13051                } else {
13052                    self.write_keyword("NOT DETERMINISTIC");
13053                }
13054            } else {
13055                // PostgreSQL and others use IMMUTABLE/VOLATILE
13056                if det {
13057                    self.write_keyword("IMMUTABLE");
13058                } else {
13059                    self.write_keyword("VOLATILE");
13060                }
13061            }
13062        }
13063        Ok(())
13064    }
13065
13066    /// Generate null input handling clause
13067    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13068        if let Some(returns_null) = cf.returns_null_on_null_input {
13069            self.write_space();
13070            if returns_null {
13071                if cf.strict {
13072                    self.write_keyword("STRICT");
13073                } else {
13074                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13075                }
13076            } else {
13077                self.write_keyword("CALLED ON NULL INPUT");
13078            }
13079        }
13080        Ok(())
13081    }
13082
13083    /// Generate security clause
13084    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13085        if let Some(security) = &cf.security {
13086            self.write_space();
13087            // MySQL uses SQL SECURITY prefix
13088            if matches!(
13089                self.config.dialect,
13090                Some(crate::dialects::DialectType::MySQL)
13091            ) {
13092                self.write_keyword("SQL SECURITY");
13093            } else {
13094                self.write_keyword("SECURITY");
13095            }
13096            self.write_space();
13097            match security {
13098                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13099                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13100                FunctionSecurity::None => self.write_keyword("NONE"),
13101            }
13102        }
13103        Ok(())
13104    }
13105
13106    /// Generate SQL data access clause
13107    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13108        if let Some(sql_data) = &cf.sql_data_access {
13109            self.write_space();
13110            match sql_data {
13111                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13112                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13113                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13114                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13115            }
13116        }
13117        Ok(())
13118    }
13119
13120    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13121        for (i, param) in params.iter().enumerate() {
13122            if i > 0 {
13123                self.write(", ");
13124            }
13125
13126            if let Some(mode) = &param.mode {
13127                if let Some(text) = &param.mode_text {
13128                    self.write(text);
13129                } else {
13130                    match mode {
13131                        ParameterMode::In => self.write_keyword("IN"),
13132                        ParameterMode::Out => self.write_keyword("OUT"),
13133                        ParameterMode::InOut => self.write_keyword("INOUT"),
13134                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13135                    }
13136                }
13137                self.write_space();
13138            }
13139
13140            if let Some(name) = &param.name {
13141                self.generate_identifier(name)?;
13142                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13143                let skip_type =
13144                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13145                if !skip_type {
13146                    self.write_space();
13147                    self.generate_data_type(&param.data_type)?;
13148                }
13149            } else {
13150                self.generate_data_type(&param.data_type)?;
13151            }
13152
13153            if let Some(default) = &param.default {
13154                if self.config.parameter_default_equals {
13155                    self.write(" = ");
13156                } else {
13157                    self.write(" DEFAULT ");
13158                }
13159                self.generate_expression(default)?;
13160            }
13161        }
13162
13163        Ok(())
13164    }
13165
13166    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13167        self.write_keyword("DROP FUNCTION");
13168
13169        if df.if_exists {
13170            self.write_space();
13171            self.write_keyword("IF EXISTS");
13172        }
13173
13174        self.write_space();
13175        self.generate_table(&df.name)?;
13176
13177        if let Some(params) = &df.parameters {
13178            self.write(" (");
13179            for (i, dt) in params.iter().enumerate() {
13180                if i > 0 {
13181                    self.write(", ");
13182                }
13183                self.generate_data_type(dt)?;
13184            }
13185            self.write(")");
13186        }
13187
13188        if df.cascade {
13189            self.write_space();
13190            self.write_keyword("CASCADE");
13191        }
13192
13193        Ok(())
13194    }
13195
13196    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13197        self.write_keyword("CREATE");
13198
13199        if cp.or_alter {
13200            self.write_space();
13201            self.write_keyword("OR ALTER");
13202        } else if cp.or_replace {
13203            self.write_space();
13204            self.write_keyword("OR REPLACE");
13205        }
13206
13207        self.write_space();
13208        if cp.use_proc_keyword {
13209            self.write_keyword("PROC");
13210        } else {
13211            self.write_keyword("PROCEDURE");
13212        }
13213
13214        if cp.if_not_exists {
13215            self.write_space();
13216            self.write_keyword("IF NOT EXISTS");
13217        }
13218
13219        self.write_space();
13220        self.generate_table(&cp.name)?;
13221        if cp.has_parens {
13222            self.write("(");
13223            self.generate_function_parameters(&cp.parameters)?;
13224            self.write(")");
13225        } else if !cp.parameters.is_empty() {
13226            // TSQL: unparenthesized parameters
13227            self.write_space();
13228            self.generate_function_parameters(&cp.parameters)?;
13229        }
13230
13231        // RETURNS clause (Snowflake)
13232        if let Some(return_type) = &cp.return_type {
13233            self.write_space();
13234            self.write_keyword("RETURNS");
13235            self.write_space();
13236            self.generate_data_type(return_type)?;
13237        }
13238
13239        // EXECUTE AS clause (Snowflake)
13240        if let Some(execute_as) = &cp.execute_as {
13241            self.write_space();
13242            self.write_keyword("EXECUTE AS");
13243            self.write_space();
13244            self.write_keyword(execute_as);
13245        }
13246
13247        if let Some(lang) = &cp.language {
13248            self.write_space();
13249            self.write_keyword("LANGUAGE");
13250            self.write_space();
13251            self.write(lang);
13252        }
13253
13254        if let Some(security) = &cp.security {
13255            self.write_space();
13256            self.write_keyword("SECURITY");
13257            self.write_space();
13258            match security {
13259                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13260                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13261                FunctionSecurity::None => self.write_keyword("NONE"),
13262            }
13263        }
13264
13265        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13266        if !cp.with_options.is_empty() {
13267            self.write_space();
13268            self.write_keyword("WITH");
13269            self.write_space();
13270            for (i, opt) in cp.with_options.iter().enumerate() {
13271                if i > 0 {
13272                    self.write(", ");
13273                }
13274                self.write(opt);
13275            }
13276        }
13277
13278        if let Some(body) = &cp.body {
13279            self.write_space();
13280            match body {
13281                FunctionBody::Block(block) => {
13282                    self.write_keyword("AS");
13283                    if matches!(
13284                        self.config.dialect,
13285                        Some(crate::dialects::DialectType::TSQL)
13286                    ) {
13287                        self.write(" BEGIN ");
13288                        self.write(block);
13289                        self.write(" END");
13290                    } else if matches!(
13291                        self.config.dialect,
13292                        Some(crate::dialects::DialectType::PostgreSQL)
13293                    ) {
13294                        self.write(" $$");
13295                        self.write(block);
13296                        self.write("$$");
13297                    } else {
13298                        // Escape content for single-quoted output
13299                        let escaped = self.escape_block_for_single_quote(block);
13300                        self.write(" '");
13301                        self.write(&escaped);
13302                        self.write("'");
13303                    }
13304                }
13305                FunctionBody::StringLiteral(s) => {
13306                    self.write_keyword("AS");
13307                    self.write(" '");
13308                    self.write(s);
13309                    self.write("'");
13310                }
13311                FunctionBody::Expression(expr) => {
13312                    self.write_keyword("AS");
13313                    self.write_space();
13314                    self.generate_expression(expr)?;
13315                }
13316                FunctionBody::External(name) => {
13317                    self.write_keyword("EXTERNAL NAME");
13318                    self.write(" '");
13319                    self.write(name);
13320                    self.write("'");
13321                }
13322                FunctionBody::Return(expr) => {
13323                    self.write_keyword("RETURN");
13324                    self.write_space();
13325                    self.generate_expression(expr)?;
13326                }
13327                FunctionBody::Statements(stmts) => {
13328                    self.write_keyword("AS");
13329                    self.write(" BEGIN ");
13330                    for (i, stmt) in stmts.iter().enumerate() {
13331                        if i > 0 {
13332                            self.write(" ");
13333                        }
13334                        self.generate_expression(stmt)?;
13335                        self.write(";");
13336                    }
13337                    self.write(" END");
13338                }
13339                FunctionBody::RawBlock(text) => {
13340                    self.write_newline();
13341                    self.write(text);
13342                }
13343                FunctionBody::DollarQuoted { content, tag } => {
13344                    self.write_keyword("AS");
13345                    self.write(" ");
13346                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13347                    let supports_dollar_quoting = matches!(
13348                        self.config.dialect,
13349                        Some(crate::dialects::DialectType::PostgreSQL)
13350                            | Some(crate::dialects::DialectType::Databricks)
13351                            | Some(crate::dialects::DialectType::Redshift)
13352                            | Some(crate::dialects::DialectType::DuckDB)
13353                    );
13354                    if supports_dollar_quoting {
13355                        // Output in dollar-quoted format
13356                        self.write("$");
13357                        if let Some(t) = tag {
13358                            self.write(t);
13359                        }
13360                        self.write("$");
13361                        self.write(content);
13362                        self.write("$");
13363                        if let Some(t) = tag {
13364                            self.write(t);
13365                        }
13366                        self.write("$");
13367                    } else {
13368                        // Convert to single-quoted string for other dialects
13369                        let escaped = self.escape_block_for_single_quote(content);
13370                        self.write("'");
13371                        self.write(&escaped);
13372                        self.write("'");
13373                    }
13374                }
13375            }
13376        }
13377
13378        Ok(())
13379    }
13380
13381    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13382        self.write_keyword("DROP PROCEDURE");
13383
13384        if dp.if_exists {
13385            self.write_space();
13386            self.write_keyword("IF EXISTS");
13387        }
13388
13389        self.write_space();
13390        self.generate_table(&dp.name)?;
13391
13392        if let Some(params) = &dp.parameters {
13393            self.write(" (");
13394            for (i, dt) in params.iter().enumerate() {
13395                if i > 0 {
13396                    self.write(", ");
13397                }
13398                self.generate_data_type(dt)?;
13399            }
13400            self.write(")");
13401        }
13402
13403        if dp.cascade {
13404            self.write_space();
13405            self.write_keyword("CASCADE");
13406        }
13407
13408        Ok(())
13409    }
13410
13411    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13412        self.write_keyword("CREATE");
13413
13414        if cs.or_replace {
13415            self.write_space();
13416            self.write_keyword("OR REPLACE");
13417        }
13418
13419        if cs.temporary {
13420            self.write_space();
13421            self.write_keyword("TEMPORARY");
13422        }
13423
13424        self.write_space();
13425        self.write_keyword("SEQUENCE");
13426
13427        if cs.if_not_exists {
13428            self.write_space();
13429            self.write_keyword("IF NOT EXISTS");
13430        }
13431
13432        self.write_space();
13433        self.generate_table(&cs.name)?;
13434
13435        // Output AS <type> if present
13436        if let Some(as_type) = &cs.as_type {
13437            self.write_space();
13438            self.write_keyword("AS");
13439            self.write_space();
13440            self.generate_data_type(as_type)?;
13441        }
13442
13443        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13444        if let Some(comment) = &cs.comment {
13445            self.write_space();
13446            self.write_keyword("COMMENT");
13447            self.write("=");
13448            self.generate_string_literal(comment)?;
13449        }
13450
13451        // If property_order is available, use it to preserve original order
13452        if !cs.property_order.is_empty() {
13453            for prop in &cs.property_order {
13454                match prop {
13455                    SeqPropKind::Start => {
13456                        if let Some(start) = cs.start {
13457                            self.write_space();
13458                            self.write_keyword("START WITH");
13459                            self.write(&format!(" {}", start));
13460                        }
13461                    }
13462                    SeqPropKind::Increment => {
13463                        if let Some(inc) = cs.increment {
13464                            self.write_space();
13465                            self.write_keyword("INCREMENT BY");
13466                            self.write(&format!(" {}", inc));
13467                        }
13468                    }
13469                    SeqPropKind::Minvalue => {
13470                        if let Some(min) = &cs.minvalue {
13471                            self.write_space();
13472                            match min {
13473                                SequenceBound::Value(v) => {
13474                                    self.write_keyword("MINVALUE");
13475                                    self.write(&format!(" {}", v));
13476                                }
13477                                SequenceBound::None => {
13478                                    self.write_keyword("NO MINVALUE");
13479                                }
13480                            }
13481                        }
13482                    }
13483                    SeqPropKind::Maxvalue => {
13484                        if let Some(max) = &cs.maxvalue {
13485                            self.write_space();
13486                            match max {
13487                                SequenceBound::Value(v) => {
13488                                    self.write_keyword("MAXVALUE");
13489                                    self.write(&format!(" {}", v));
13490                                }
13491                                SequenceBound::None => {
13492                                    self.write_keyword("NO MAXVALUE");
13493                                }
13494                            }
13495                        }
13496                    }
13497                    SeqPropKind::Cache => {
13498                        if let Some(cache) = cs.cache {
13499                            self.write_space();
13500                            self.write_keyword("CACHE");
13501                            self.write(&format!(" {}", cache));
13502                        }
13503                    }
13504                    SeqPropKind::NoCache => {
13505                        self.write_space();
13506                        self.write_keyword("NO CACHE");
13507                    }
13508                    SeqPropKind::NoCacheWord => {
13509                        self.write_space();
13510                        self.write_keyword("NOCACHE");
13511                    }
13512                    SeqPropKind::Cycle => {
13513                        self.write_space();
13514                        self.write_keyword("CYCLE");
13515                    }
13516                    SeqPropKind::NoCycle => {
13517                        self.write_space();
13518                        self.write_keyword("NO CYCLE");
13519                    }
13520                    SeqPropKind::NoCycleWord => {
13521                        self.write_space();
13522                        self.write_keyword("NOCYCLE");
13523                    }
13524                    SeqPropKind::OwnedBy => {
13525                        // Skip OWNED BY NONE (it's a no-op)
13526                        if !cs.owned_by_none {
13527                            if let Some(owned) = &cs.owned_by {
13528                                self.write_space();
13529                                self.write_keyword("OWNED BY");
13530                                self.write_space();
13531                                self.generate_table(owned)?;
13532                            }
13533                        }
13534                    }
13535                    SeqPropKind::Order => {
13536                        self.write_space();
13537                        self.write_keyword("ORDER");
13538                    }
13539                    SeqPropKind::NoOrder => {
13540                        self.write_space();
13541                        self.write_keyword("NOORDER");
13542                    }
13543                    SeqPropKind::Comment => {
13544                        // COMMENT is output above, before property_order iteration
13545                    }
13546                    SeqPropKind::Sharing => {
13547                        if let Some(val) = &cs.sharing {
13548                            self.write_space();
13549                            self.write(&format!("SHARING={}", val));
13550                        }
13551                    }
13552                    SeqPropKind::Keep => {
13553                        self.write_space();
13554                        self.write_keyword("KEEP");
13555                    }
13556                    SeqPropKind::NoKeep => {
13557                        self.write_space();
13558                        self.write_keyword("NOKEEP");
13559                    }
13560                    SeqPropKind::Scale => {
13561                        self.write_space();
13562                        self.write_keyword("SCALE");
13563                        if let Some(modifier) = &cs.scale_modifier {
13564                            if !modifier.is_empty() {
13565                                self.write_space();
13566                                self.write_keyword(modifier);
13567                            }
13568                        }
13569                    }
13570                    SeqPropKind::NoScale => {
13571                        self.write_space();
13572                        self.write_keyword("NOSCALE");
13573                    }
13574                    SeqPropKind::Shard => {
13575                        self.write_space();
13576                        self.write_keyword("SHARD");
13577                        if let Some(modifier) = &cs.shard_modifier {
13578                            if !modifier.is_empty() {
13579                                self.write_space();
13580                                self.write_keyword(modifier);
13581                            }
13582                        }
13583                    }
13584                    SeqPropKind::NoShard => {
13585                        self.write_space();
13586                        self.write_keyword("NOSHARD");
13587                    }
13588                    SeqPropKind::Session => {
13589                        self.write_space();
13590                        self.write_keyword("SESSION");
13591                    }
13592                    SeqPropKind::Global => {
13593                        self.write_space();
13594                        self.write_keyword("GLOBAL");
13595                    }
13596                    SeqPropKind::NoMinvalueWord => {
13597                        self.write_space();
13598                        self.write_keyword("NOMINVALUE");
13599                    }
13600                    SeqPropKind::NoMaxvalueWord => {
13601                        self.write_space();
13602                        self.write_keyword("NOMAXVALUE");
13603                    }
13604                }
13605            }
13606        } else {
13607            // Fallback: default order for backwards compatibility
13608            if let Some(inc) = cs.increment {
13609                self.write_space();
13610                self.write_keyword("INCREMENT BY");
13611                self.write(&format!(" {}", inc));
13612            }
13613
13614            if let Some(min) = &cs.minvalue {
13615                self.write_space();
13616                match min {
13617                    SequenceBound::Value(v) => {
13618                        self.write_keyword("MINVALUE");
13619                        self.write(&format!(" {}", v));
13620                    }
13621                    SequenceBound::None => {
13622                        self.write_keyword("NO MINVALUE");
13623                    }
13624                }
13625            }
13626
13627            if let Some(max) = &cs.maxvalue {
13628                self.write_space();
13629                match max {
13630                    SequenceBound::Value(v) => {
13631                        self.write_keyword("MAXVALUE");
13632                        self.write(&format!(" {}", v));
13633                    }
13634                    SequenceBound::None => {
13635                        self.write_keyword("NO MAXVALUE");
13636                    }
13637                }
13638            }
13639
13640            if let Some(start) = cs.start {
13641                self.write_space();
13642                self.write_keyword("START WITH");
13643                self.write(&format!(" {}", start));
13644            }
13645
13646            if let Some(cache) = cs.cache {
13647                self.write_space();
13648                self.write_keyword("CACHE");
13649                self.write(&format!(" {}", cache));
13650            }
13651
13652            if cs.cycle {
13653                self.write_space();
13654                self.write_keyword("CYCLE");
13655            }
13656
13657            if let Some(owned) = &cs.owned_by {
13658                self.write_space();
13659                self.write_keyword("OWNED BY");
13660                self.write_space();
13661                self.generate_table(owned)?;
13662            }
13663        }
13664
13665        Ok(())
13666    }
13667
13668    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13669        self.write_keyword("DROP SEQUENCE");
13670
13671        if ds.if_exists {
13672            self.write_space();
13673            self.write_keyword("IF EXISTS");
13674        }
13675
13676        self.write_space();
13677        self.generate_table(&ds.name)?;
13678
13679        if ds.cascade {
13680            self.write_space();
13681            self.write_keyword("CASCADE");
13682        }
13683
13684        Ok(())
13685    }
13686
13687    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13688        self.write_keyword("ALTER SEQUENCE");
13689
13690        if als.if_exists {
13691            self.write_space();
13692            self.write_keyword("IF EXISTS");
13693        }
13694
13695        self.write_space();
13696        self.generate_table(&als.name)?;
13697
13698        if let Some(inc) = als.increment {
13699            self.write_space();
13700            self.write_keyword("INCREMENT BY");
13701            self.write(&format!(" {}", inc));
13702        }
13703
13704        if let Some(min) = &als.minvalue {
13705            self.write_space();
13706            match min {
13707                SequenceBound::Value(v) => {
13708                    self.write_keyword("MINVALUE");
13709                    self.write(&format!(" {}", v));
13710                }
13711                SequenceBound::None => {
13712                    self.write_keyword("NO MINVALUE");
13713                }
13714            }
13715        }
13716
13717        if let Some(max) = &als.maxvalue {
13718            self.write_space();
13719            match max {
13720                SequenceBound::Value(v) => {
13721                    self.write_keyword("MAXVALUE");
13722                    self.write(&format!(" {}", v));
13723                }
13724                SequenceBound::None => {
13725                    self.write_keyword("NO MAXVALUE");
13726                }
13727            }
13728        }
13729
13730        if let Some(start) = als.start {
13731            self.write_space();
13732            self.write_keyword("START WITH");
13733            self.write(&format!(" {}", start));
13734        }
13735
13736        if let Some(restart) = &als.restart {
13737            self.write_space();
13738            self.write_keyword("RESTART");
13739            if let Some(val) = restart {
13740                self.write_keyword(" WITH");
13741                self.write(&format!(" {}", val));
13742            }
13743        }
13744
13745        if let Some(cache) = als.cache {
13746            self.write_space();
13747            self.write_keyword("CACHE");
13748            self.write(&format!(" {}", cache));
13749        }
13750
13751        if let Some(cycle) = als.cycle {
13752            self.write_space();
13753            if cycle {
13754                self.write_keyword("CYCLE");
13755            } else {
13756                self.write_keyword("NO CYCLE");
13757            }
13758        }
13759
13760        if let Some(owned) = &als.owned_by {
13761            self.write_space();
13762            self.write_keyword("OWNED BY");
13763            self.write_space();
13764            if let Some(table) = owned {
13765                self.generate_table(table)?;
13766            } else {
13767                self.write_keyword("NONE");
13768            }
13769        }
13770
13771        Ok(())
13772    }
13773
13774    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13775        self.write_keyword("CREATE");
13776
13777        if ct.or_alter {
13778            self.write_space();
13779            self.write_keyword("OR ALTER");
13780        } else if ct.or_replace {
13781            self.write_space();
13782            self.write_keyword("OR REPLACE");
13783        }
13784
13785        if ct.constraint {
13786            self.write_space();
13787            self.write_keyword("CONSTRAINT");
13788        }
13789
13790        self.write_space();
13791        self.write_keyword("TRIGGER");
13792        self.write_space();
13793        self.generate_identifier(&ct.name)?;
13794
13795        self.write_space();
13796        match ct.timing {
13797            TriggerTiming::Before => self.write_keyword("BEFORE"),
13798            TriggerTiming::After => self.write_keyword("AFTER"),
13799            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13800        }
13801
13802        // Events
13803        for (i, event) in ct.events.iter().enumerate() {
13804            if i > 0 {
13805                self.write_keyword(" OR");
13806            }
13807            self.write_space();
13808            match event {
13809                TriggerEvent::Insert => self.write_keyword("INSERT"),
13810                TriggerEvent::Update(cols) => {
13811                    self.write_keyword("UPDATE");
13812                    if let Some(cols) = cols {
13813                        self.write_space();
13814                        self.write_keyword("OF");
13815                        for (j, col) in cols.iter().enumerate() {
13816                            if j > 0 {
13817                                self.write(",");
13818                            }
13819                            self.write_space();
13820                            self.generate_identifier(col)?;
13821                        }
13822                    }
13823                }
13824                TriggerEvent::Delete => self.write_keyword("DELETE"),
13825                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13826            }
13827        }
13828
13829        self.write_space();
13830        self.write_keyword("ON");
13831        self.write_space();
13832        self.generate_table(&ct.table)?;
13833
13834        // Referencing clause
13835        if let Some(ref_clause) = &ct.referencing {
13836            self.write_space();
13837            self.write_keyword("REFERENCING");
13838            if let Some(old_table) = &ref_clause.old_table {
13839                self.write_space();
13840                self.write_keyword("OLD TABLE AS");
13841                self.write_space();
13842                self.generate_identifier(old_table)?;
13843            }
13844            if let Some(new_table) = &ref_clause.new_table {
13845                self.write_space();
13846                self.write_keyword("NEW TABLE AS");
13847                self.write_space();
13848                self.generate_identifier(new_table)?;
13849            }
13850            if let Some(old_row) = &ref_clause.old_row {
13851                self.write_space();
13852                self.write_keyword("OLD ROW AS");
13853                self.write_space();
13854                self.generate_identifier(old_row)?;
13855            }
13856            if let Some(new_row) = &ref_clause.new_row {
13857                self.write_space();
13858                self.write_keyword("NEW ROW AS");
13859                self.write_space();
13860                self.generate_identifier(new_row)?;
13861            }
13862        }
13863
13864        // Deferrable options for constraint triggers (must come before FOR EACH)
13865        if let Some(deferrable) = ct.deferrable {
13866            self.write_space();
13867            if deferrable {
13868                self.write_keyword("DEFERRABLE");
13869            } else {
13870                self.write_keyword("NOT DEFERRABLE");
13871            }
13872        }
13873
13874        if let Some(initially) = ct.initially_deferred {
13875            self.write_space();
13876            self.write_keyword("INITIALLY");
13877            self.write_space();
13878            if initially {
13879                self.write_keyword("DEFERRED");
13880            } else {
13881                self.write_keyword("IMMEDIATE");
13882            }
13883        }
13884
13885        if let Some(for_each) = ct.for_each {
13886            self.write_space();
13887            self.write_keyword("FOR EACH");
13888            self.write_space();
13889            match for_each {
13890                TriggerForEach::Row => self.write_keyword("ROW"),
13891                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13892            }
13893        }
13894
13895        // When clause
13896        if let Some(when) = &ct.when {
13897            self.write_space();
13898            self.write_keyword("WHEN");
13899            if ct.when_paren {
13900                self.write(" (");
13901                self.generate_expression(when)?;
13902                self.write(")");
13903            } else {
13904                self.write_space();
13905                self.generate_expression(when)?;
13906            }
13907        }
13908
13909        // Body
13910        self.write_space();
13911        match &ct.body {
13912            TriggerBody::Execute { function, args } => {
13913                self.write_keyword("EXECUTE FUNCTION");
13914                self.write_space();
13915                self.generate_table(function)?;
13916                self.write("(");
13917                for (i, arg) in args.iter().enumerate() {
13918                    if i > 0 {
13919                        self.write(", ");
13920                    }
13921                    self.generate_expression(arg)?;
13922                }
13923                self.write(")");
13924            }
13925            TriggerBody::Block(block) => {
13926                self.write_keyword("BEGIN");
13927                self.write_space();
13928                self.write(block);
13929                self.write_space();
13930                self.write_keyword("END");
13931            }
13932        }
13933
13934        Ok(())
13935    }
13936
13937    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13938        self.write_keyword("DROP TRIGGER");
13939
13940        if dt.if_exists {
13941            self.write_space();
13942            self.write_keyword("IF EXISTS");
13943        }
13944
13945        self.write_space();
13946        self.generate_identifier(&dt.name)?;
13947
13948        if let Some(table) = &dt.table {
13949            self.write_space();
13950            self.write_keyword("ON");
13951            self.write_space();
13952            self.generate_table(table)?;
13953        }
13954
13955        if dt.cascade {
13956            self.write_space();
13957            self.write_keyword("CASCADE");
13958        }
13959
13960        Ok(())
13961    }
13962
13963    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13964        self.write_keyword("CREATE TYPE");
13965
13966        if ct.if_not_exists {
13967            self.write_space();
13968            self.write_keyword("IF NOT EXISTS");
13969        }
13970
13971        self.write_space();
13972        self.generate_table(&ct.name)?;
13973
13974        self.write_space();
13975        self.write_keyword("AS");
13976        self.write_space();
13977
13978        match &ct.definition {
13979            TypeDefinition::Enum(values) => {
13980                self.write_keyword("ENUM");
13981                self.write(" (");
13982                for (i, val) in values.iter().enumerate() {
13983                    if i > 0 {
13984                        self.write(", ");
13985                    }
13986                    self.write(&format!("'{}'", val));
13987                }
13988                self.write(")");
13989            }
13990            TypeDefinition::Composite(attrs) => {
13991                self.write("(");
13992                for (i, attr) in attrs.iter().enumerate() {
13993                    if i > 0 {
13994                        self.write(", ");
13995                    }
13996                    self.generate_identifier(&attr.name)?;
13997                    self.write_space();
13998                    self.generate_data_type(&attr.data_type)?;
13999                    if let Some(collate) = &attr.collate {
14000                        self.write_space();
14001                        self.write_keyword("COLLATE");
14002                        self.write_space();
14003                        self.generate_identifier(collate)?;
14004                    }
14005                }
14006                self.write(")");
14007            }
14008            TypeDefinition::Range {
14009                subtype,
14010                subtype_diff,
14011                canonical,
14012            } => {
14013                self.write_keyword("RANGE");
14014                self.write(" (");
14015                self.write_keyword("SUBTYPE");
14016                self.write(" = ");
14017                self.generate_data_type(subtype)?;
14018                if let Some(diff) = subtype_diff {
14019                    self.write(", ");
14020                    self.write_keyword("SUBTYPE_DIFF");
14021                    self.write(" = ");
14022                    self.write(diff);
14023                }
14024                if let Some(canon) = canonical {
14025                    self.write(", ");
14026                    self.write_keyword("CANONICAL");
14027                    self.write(" = ");
14028                    self.write(canon);
14029                }
14030                self.write(")");
14031            }
14032            TypeDefinition::Base {
14033                input,
14034                output,
14035                internallength,
14036            } => {
14037                self.write("(");
14038                self.write_keyword("INPUT");
14039                self.write(" = ");
14040                self.write(input);
14041                self.write(", ");
14042                self.write_keyword("OUTPUT");
14043                self.write(" = ");
14044                self.write(output);
14045                if let Some(len) = internallength {
14046                    self.write(", ");
14047                    self.write_keyword("INTERNALLENGTH");
14048                    self.write(" = ");
14049                    self.write(&len.to_string());
14050                }
14051                self.write(")");
14052            }
14053            TypeDefinition::Domain {
14054                base_type,
14055                default,
14056                constraints,
14057            } => {
14058                self.generate_data_type(base_type)?;
14059                if let Some(def) = default {
14060                    self.write_space();
14061                    self.write_keyword("DEFAULT");
14062                    self.write_space();
14063                    self.generate_expression(def)?;
14064                }
14065                for constr in constraints {
14066                    self.write_space();
14067                    if let Some(name) = &constr.name {
14068                        self.write_keyword("CONSTRAINT");
14069                        self.write_space();
14070                        self.generate_identifier(name)?;
14071                        self.write_space();
14072                    }
14073                    self.write_keyword("CHECK");
14074                    self.write(" (");
14075                    self.generate_expression(&constr.check)?;
14076                    self.write(")");
14077                }
14078            }
14079        }
14080
14081        Ok(())
14082    }
14083
14084    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14085        self.write_keyword("CREATE");
14086        if task.or_replace {
14087            self.write_space();
14088            self.write_keyword("OR REPLACE");
14089        }
14090        self.write_space();
14091        self.write_keyword("TASK");
14092        if task.if_not_exists {
14093            self.write_space();
14094            self.write_keyword("IF NOT EXISTS");
14095        }
14096        self.write_space();
14097        self.write(&task.name);
14098        if !task.properties.is_empty() {
14099            // Properties already include leading whitespace from tokens_to_sql
14100            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14101                self.write_space();
14102            }
14103            self.write(&task.properties);
14104        }
14105        self.write_space();
14106        self.write_keyword("AS");
14107        self.write_space();
14108        self.generate_expression(&task.body)?;
14109        Ok(())
14110    }
14111
14112    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14113        self.write_keyword("DROP TYPE");
14114
14115        if dt.if_exists {
14116            self.write_space();
14117            self.write_keyword("IF EXISTS");
14118        }
14119
14120        self.write_space();
14121        self.generate_table(&dt.name)?;
14122
14123        if dt.cascade {
14124            self.write_space();
14125            self.write_keyword("CASCADE");
14126        }
14127
14128        Ok(())
14129    }
14130
14131    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14132        // Athena: DESCRIBE uses Hive engine (backticks)
14133        let saved_athena_hive_context = self.athena_hive_context;
14134        if matches!(
14135            self.config.dialect,
14136            Some(crate::dialects::DialectType::Athena)
14137        ) {
14138            self.athena_hive_context = true;
14139        }
14140
14141        // Output leading comments before DESCRIBE
14142        for comment in &d.leading_comments {
14143            self.write_formatted_comment(comment);
14144            self.write(" ");
14145        }
14146
14147        self.write_keyword("DESCRIBE");
14148
14149        if d.extended {
14150            self.write_space();
14151            self.write_keyword("EXTENDED");
14152        } else if d.formatted {
14153            self.write_space();
14154            self.write_keyword("FORMATTED");
14155        }
14156
14157        // Output style like ANALYZE, HISTORY
14158        if let Some(ref style) = d.style {
14159            self.write_space();
14160            self.write_keyword(style);
14161        }
14162
14163        // Handle object kind (TABLE, VIEW) based on dialect
14164        let should_output_kind = match self.config.dialect {
14165            // Spark doesn't use TABLE/VIEW after DESCRIBE
14166            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14167                false
14168            }
14169            // Snowflake always includes TABLE
14170            Some(DialectType::Snowflake) => true,
14171            _ => d.kind.is_some(),
14172        };
14173        if should_output_kind {
14174            if let Some(ref kind) = d.kind {
14175                self.write_space();
14176                self.write_keyword(kind);
14177            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14178                self.write_space();
14179                self.write_keyword("TABLE");
14180            }
14181        }
14182
14183        self.write_space();
14184        self.generate_expression(&d.target)?;
14185
14186        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14187        if !d.params.is_empty() {
14188            self.write("(");
14189            for (i, param) in d.params.iter().enumerate() {
14190                if i > 0 {
14191                    self.write(", ");
14192                }
14193                self.write(param);
14194            }
14195            self.write(")");
14196        }
14197
14198        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14199        if let Some(ref partition) = d.partition {
14200            self.write_space();
14201            self.generate_expression(partition)?;
14202        }
14203
14204        // Databricks: AS JSON
14205        if d.as_json {
14206            self.write_space();
14207            self.write_keyword("AS JSON");
14208        }
14209
14210        // Output properties like type=stage
14211        for (name, value) in &d.properties {
14212            self.write_space();
14213            self.write(name);
14214            self.write("=");
14215            self.write(value);
14216        }
14217
14218        // Restore Athena Hive context
14219        self.athena_hive_context = saved_athena_hive_context;
14220
14221        Ok(())
14222    }
14223
14224    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14225    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14226    fn generate_show(&mut self, s: &Show) -> Result<()> {
14227        self.write_keyword("SHOW");
14228        self.write_space();
14229
14230        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14231        // where TERSE is syntactically valid but has no effect on output
14232        let show_terse = s.terse
14233            && !matches!(
14234                s.this.as_str(),
14235                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14236            );
14237        if show_terse {
14238            self.write_keyword("TERSE");
14239            self.write_space();
14240        }
14241
14242        // Object type (USERS, TABLES, DATABASES, etc.)
14243        self.write_keyword(&s.this);
14244
14245        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14246        if let Some(ref target_expr) = s.target {
14247            self.write_space();
14248            self.generate_expression(target_expr)?;
14249        }
14250
14251        // HISTORY keyword
14252        if s.history {
14253            self.write_space();
14254            self.write_keyword("HISTORY");
14255        }
14256
14257        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14258        if let Some(ref for_target) = s.for_target {
14259            self.write_space();
14260            self.write_keyword("FOR");
14261            self.write_space();
14262            self.generate_expression(for_target)?;
14263        }
14264
14265        // Determine ordering based on dialect:
14266        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14267        // - MySQL: IN, FROM, LIKE (when FROM is present)
14268        use crate::dialects::DialectType;
14269        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14270        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14271        let mysql_tables_scope_as_from = is_mysql
14272            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14273            && s.scope_kind.as_deref() == Some("SCHEMA")
14274            && s.scope.is_some()
14275            && s.from.is_none();
14276
14277        if !is_snowflake && s.from.is_some() {
14278            // MySQL ordering: IN, FROM, LIKE
14279
14280            // IN scope_kind [scope]
14281            if let Some(ref scope_kind) = s.scope_kind {
14282                self.write_space();
14283                self.write_keyword("IN");
14284                self.write_space();
14285                self.write_keyword(scope_kind);
14286                if let Some(ref scope) = s.scope {
14287                    self.write_space();
14288                    self.generate_expression(scope)?;
14289                }
14290            } else if let Some(ref scope) = s.scope {
14291                self.write_space();
14292                self.write_keyword("IN");
14293                self.write_space();
14294                self.generate_expression(scope)?;
14295            }
14296
14297            // FROM clause
14298            if let Some(ref from) = s.from {
14299                self.write_space();
14300                self.write_keyword("FROM");
14301                self.write_space();
14302                self.generate_expression(from)?;
14303            }
14304
14305            // Second FROM clause (db name)
14306            if let Some(ref db) = s.db {
14307                self.write_space();
14308                self.write_keyword("FROM");
14309                self.write_space();
14310                self.generate_expression(db)?;
14311            }
14312
14313            // LIKE pattern
14314            if let Some(ref like) = s.like {
14315                self.write_space();
14316                self.write_keyword("LIKE");
14317                self.write_space();
14318                self.generate_expression(like)?;
14319            }
14320        } else {
14321            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14322
14323            // LIKE pattern
14324            if let Some(ref like) = s.like {
14325                self.write_space();
14326                self.write_keyword("LIKE");
14327                self.write_space();
14328                self.generate_expression(like)?;
14329            }
14330
14331            // IN scope_kind [scope]
14332            if mysql_tables_scope_as_from {
14333                self.write_space();
14334                self.write_keyword("FROM");
14335                self.write_space();
14336                self.generate_expression(s.scope.as_ref().unwrap())?;
14337            } else if let Some(ref scope_kind) = s.scope_kind {
14338                self.write_space();
14339                self.write_keyword("IN");
14340                self.write_space();
14341                self.write_keyword(scope_kind);
14342                if let Some(ref scope) = s.scope {
14343                    self.write_space();
14344                    self.generate_expression(scope)?;
14345                }
14346            } else if let Some(ref scope) = s.scope {
14347                self.write_space();
14348                self.write_keyword("IN");
14349                self.write_space();
14350                self.generate_expression(scope)?;
14351            }
14352        }
14353
14354        // STARTS WITH pattern
14355        if let Some(ref starts_with) = s.starts_with {
14356            self.write_space();
14357            self.write_keyword("STARTS WITH");
14358            self.write_space();
14359            self.generate_expression(starts_with)?;
14360        }
14361
14362        // LIMIT clause
14363        if let Some(ref limit) = s.limit {
14364            self.write_space();
14365            self.generate_limit(limit)?;
14366        }
14367
14368        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14369        if is_snowflake {
14370            if let Some(ref from) = s.from {
14371                self.write_space();
14372                self.write_keyword("FROM");
14373                self.write_space();
14374                self.generate_expression(from)?;
14375            }
14376        }
14377
14378        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14379        if let Some(ref where_clause) = s.where_clause {
14380            self.write_space();
14381            self.write_keyword("WHERE");
14382            self.write_space();
14383            self.generate_expression(where_clause)?;
14384        }
14385
14386        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14387        if let Some(is_mutex) = s.mutex {
14388            self.write_space();
14389            if is_mutex {
14390                self.write_keyword("MUTEX");
14391            } else {
14392                self.write_keyword("STATUS");
14393            }
14394        }
14395
14396        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14397        if !s.privileges.is_empty() {
14398            self.write_space();
14399            self.write_keyword("WITH PRIVILEGES");
14400            self.write_space();
14401            for (i, priv_name) in s.privileges.iter().enumerate() {
14402                if i > 0 {
14403                    self.write(", ");
14404                }
14405                self.write_keyword(priv_name);
14406            }
14407        }
14408
14409        Ok(())
14410    }
14411
14412    // ==================== End DDL Generation ====================
14413
14414    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14415        use crate::dialects::DialectType;
14416        match lit {
14417            Literal::String(s) => {
14418                self.generate_string_literal(s)?;
14419            }
14420            Literal::Number(n) => {
14421                if matches!(self.config.dialect, Some(DialectType::MySQL))
14422                    && n.len() > 2
14423                    && (n.starts_with("0x") || n.starts_with("0X"))
14424                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14425                {
14426                    return self.generate_identifier(&Identifier {
14427                        name: n.clone(),
14428                        quoted: true,
14429                        trailing_comments: Vec::new(),
14430                        span: None,
14431                    });
14432                }
14433                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14434                // for dialects that don't support them (MySQL interprets as identifier).
14435                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14436                let n = if n.contains('_')
14437                    && !matches!(
14438                        self.config.dialect,
14439                        Some(DialectType::ClickHouse)
14440                            | Some(DialectType::DuckDB)
14441                            | Some(DialectType::PostgreSQL)
14442                            | Some(DialectType::Hive)
14443                            | Some(DialectType::Spark)
14444                            | Some(DialectType::Databricks)
14445                    ) {
14446                    std::borrow::Cow::Owned(n.replace('_', ""))
14447                } else {
14448                    std::borrow::Cow::Borrowed(n.as_str())
14449                };
14450                // Normalize numbers starting with decimal point to have leading zero
14451                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14452                if n.starts_with('.') {
14453                    self.write("0");
14454                    self.write(&n);
14455                } else if n.starts_with("-.") {
14456                    // Handle negative numbers like -.25 -> -0.25
14457                    self.write("-0");
14458                    self.write(&n[1..]);
14459                } else {
14460                    self.write(&n);
14461                }
14462            }
14463            Literal::HexString(h) => {
14464                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14465                match self.config.dialect {
14466                    Some(DialectType::Spark)
14467                    | Some(DialectType::Databricks)
14468                    | Some(DialectType::Teradata) => self.write("X'"),
14469                    _ => self.write("x'"),
14470                }
14471                self.write(h);
14472                self.write("'");
14473            }
14474            Literal::HexNumber(h) => {
14475                // Hex number (0xA) - integer in hex notation (from BigQuery)
14476                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14477                // For other dialects, convert to decimal integer
14478                match self.config.dialect {
14479                    Some(DialectType::BigQuery)
14480                    | Some(DialectType::ClickHouse)
14481                    | Some(DialectType::TSQL)
14482                    | Some(DialectType::Fabric) => {
14483                        self.write("0x");
14484                        self.write(h);
14485                    }
14486                    _ => {
14487                        // Convert hex to decimal
14488                        if let Ok(val) = u64::from_str_radix(h, 16) {
14489                            self.write(&val.to_string());
14490                        } else {
14491                            // Fallback: keep as 0x notation
14492                            self.write("0x");
14493                            self.write(h);
14494                        }
14495                    }
14496                }
14497            }
14498            Literal::BitString(b) => {
14499                // Bit string B'0101...'
14500                self.write("B'");
14501                self.write(b);
14502                self.write("'");
14503            }
14504            Literal::ByteString(b) => {
14505                // Byte string b'...' (BigQuery style)
14506                self.write("b'");
14507                // Escape special characters for output
14508                self.write_escaped_byte_string(b);
14509                self.write("'");
14510            }
14511            Literal::NationalString(s) => {
14512                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14513                // Other dialects strip the N prefix and output as regular string
14514                let keep_n_prefix = matches!(
14515                    self.config.dialect,
14516                    Some(DialectType::TSQL)
14517                        | Some(DialectType::Oracle)
14518                        | Some(DialectType::MySQL)
14519                        | None
14520                );
14521                if keep_n_prefix {
14522                    self.write("N'");
14523                } else {
14524                    self.write("'");
14525                }
14526                self.write(s);
14527                self.write("'");
14528            }
14529            Literal::Date(d) => {
14530                self.generate_date_literal(d)?;
14531            }
14532            Literal::Time(t) => {
14533                self.generate_time_literal(t)?;
14534            }
14535            Literal::Timestamp(ts) => {
14536                self.generate_timestamp_literal(ts)?;
14537            }
14538            Literal::Datetime(dt) => {
14539                self.generate_datetime_literal(dt)?;
14540            }
14541            Literal::TripleQuotedString(s, _quote_char) => {
14542                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14543                if matches!(
14544                    self.config.dialect,
14545                    Some(crate::dialects::DialectType::BigQuery)
14546                        | Some(crate::dialects::DialectType::DuckDB)
14547                        | Some(crate::dialects::DialectType::Snowflake)
14548                        | Some(crate::dialects::DialectType::Spark)
14549                        | Some(crate::dialects::DialectType::Hive)
14550                        | Some(crate::dialects::DialectType::Presto)
14551                        | Some(crate::dialects::DialectType::Trino)
14552                        | Some(crate::dialects::DialectType::PostgreSQL)
14553                        | Some(crate::dialects::DialectType::MySQL)
14554                        | Some(crate::dialects::DialectType::Redshift)
14555                        | Some(crate::dialects::DialectType::TSQL)
14556                        | Some(crate::dialects::DialectType::Oracle)
14557                        | Some(crate::dialects::DialectType::ClickHouse)
14558                        | Some(crate::dialects::DialectType::Databricks)
14559                        | Some(crate::dialects::DialectType::SQLite)
14560                ) {
14561                    self.generate_string_literal(s)?;
14562                } else {
14563                    // Preserve triple-quoted string syntax for generic/unknown dialects
14564                    let quotes = format!("{0}{0}{0}", _quote_char);
14565                    self.write(&quotes);
14566                    self.write(s);
14567                    self.write(&quotes);
14568                }
14569            }
14570            Literal::EscapeString(s) => {
14571                // PostgreSQL escape string: e'...' or E'...'
14572                // Token text format is "e:content" or "E:content"
14573                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14574                use crate::dialects::DialectType;
14575                let content = if let Some(c) = s.strip_prefix("e:") {
14576                    c
14577                } else if let Some(c) = s.strip_prefix("E:") {
14578                    c
14579                } else {
14580                    s.as_str()
14581                };
14582
14583                // MySQL: output the content without quotes or prefix
14584                if matches!(
14585                    self.config.dialect,
14586                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14587                ) {
14588                    self.write(content);
14589                } else {
14590                    // Some dialects use lowercase e' prefix
14591                    let prefix = if matches!(
14592                        self.config.dialect,
14593                        Some(DialectType::SingleStore)
14594                            | Some(DialectType::DuckDB)
14595                            | Some(DialectType::PostgreSQL)
14596                            | Some(DialectType::CockroachDB)
14597                            | Some(DialectType::Materialize)
14598                            | Some(DialectType::RisingWave)
14599                    ) {
14600                        "e'"
14601                    } else {
14602                        "E'"
14603                    };
14604
14605                    // Normalize \' to '' for output
14606                    let normalized = content.replace("\\'", "''");
14607                    self.write(prefix);
14608                    self.write(&normalized);
14609                    self.write("'");
14610                }
14611            }
14612            Literal::DollarString(s) => {
14613                // Convert dollar-quoted strings to single-quoted strings
14614                // (like Python sqlglot's rawstring_sql)
14615                use crate::dialects::DialectType;
14616                // Extract content from tag\x00content format
14617                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14618                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14619                let escape_backslash = matches!(
14620                    self.config.dialect,
14621                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
14622                );
14623                // Step 2: Determine quote escaping style
14624                // Snowflake: ' -> \' (backslash escape)
14625                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14626                let use_backslash_quote =
14627                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14628
14629                let mut escaped = String::with_capacity(content.len() + 4);
14630                for ch in content.chars() {
14631                    if escape_backslash && ch == '\\' {
14632                        // Escape backslash first (before quote escaping)
14633                        escaped.push('\\');
14634                        escaped.push('\\');
14635                    } else if ch == '\'' {
14636                        if use_backslash_quote {
14637                            escaped.push('\\');
14638                            escaped.push('\'');
14639                        } else {
14640                            escaped.push('\'');
14641                            escaped.push('\'');
14642                        }
14643                    } else {
14644                        escaped.push(ch);
14645                    }
14646                }
14647                self.write("'");
14648                self.write(&escaped);
14649                self.write("'");
14650            }
14651            Literal::RawString(s) => {
14652                // Raw strings (r"..." or r'...') contain literal backslashes.
14653                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14654                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14655                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14656                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14657                use crate::dialects::DialectType;
14658
14659                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14660                let escape_backslash = matches!(
14661                    self.config.dialect,
14662                    Some(DialectType::BigQuery)
14663                        | Some(DialectType::MySQL)
14664                        | Some(DialectType::SingleStore)
14665                        | Some(DialectType::TiDB)
14666                        | Some(DialectType::Hive)
14667                        | Some(DialectType::Spark)
14668                        | Some(DialectType::Databricks)
14669                        | Some(DialectType::Drill)
14670                        | Some(DialectType::Snowflake)
14671                        | Some(DialectType::Redshift)
14672                        | Some(DialectType::ClickHouse)
14673                );
14674
14675                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14676                // These escape quotes as \' instead of ''
14677                let backslash_escapes_quote = matches!(
14678                    self.config.dialect,
14679                    Some(DialectType::BigQuery)
14680                        | Some(DialectType::Hive)
14681                        | Some(DialectType::Spark)
14682                        | Some(DialectType::Databricks)
14683                        | Some(DialectType::Drill)
14684                        | Some(DialectType::Snowflake)
14685                        | Some(DialectType::Redshift)
14686                );
14687
14688                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14689                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14690                let supports_escape_sequences = escape_backslash;
14691
14692                let mut escaped = String::with_capacity(s.len() + 4);
14693                for ch in s.chars() {
14694                    if escape_backslash && ch == '\\' {
14695                        // Double the backslash for the target dialect
14696                        escaped.push('\\');
14697                        escaped.push('\\');
14698                    } else if ch == '\'' {
14699                        if backslash_escapes_quote {
14700                            // Use backslash to escape the quote: \'
14701                            escaped.push('\\');
14702                            escaped.push('\'');
14703                        } else {
14704                            // Use SQL standard quote doubling: ''
14705                            escaped.push('\'');
14706                            escaped.push('\'');
14707                        }
14708                    } else if supports_escape_sequences {
14709                        // Apply ESCAPED_SEQUENCES mapping for special chars
14710                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14711                        match ch {
14712                            '\n' => {
14713                                escaped.push('\\');
14714                                escaped.push('n');
14715                            }
14716                            '\r' => {
14717                                escaped.push('\\');
14718                                escaped.push('r');
14719                            }
14720                            '\t' => {
14721                                escaped.push('\\');
14722                                escaped.push('t');
14723                            }
14724                            '\x07' => {
14725                                escaped.push('\\');
14726                                escaped.push('a');
14727                            }
14728                            '\x08' => {
14729                                escaped.push('\\');
14730                                escaped.push('b');
14731                            }
14732                            '\x0C' => {
14733                                escaped.push('\\');
14734                                escaped.push('f');
14735                            }
14736                            '\x0B' => {
14737                                escaped.push('\\');
14738                                escaped.push('v');
14739                            }
14740                            _ => escaped.push(ch),
14741                        }
14742                    } else {
14743                        escaped.push(ch);
14744                    }
14745                }
14746                self.write("'");
14747                self.write(&escaped);
14748                self.write("'");
14749            }
14750        }
14751        Ok(())
14752    }
14753
14754    /// Generate a DATE literal with dialect-specific formatting
14755    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14756        use crate::dialects::DialectType;
14757
14758        match self.config.dialect {
14759            // SQL Server / Fabric use CONVERT or CAST
14760            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14761                self.write("CAST('");
14762                self.write(d);
14763                self.write("' AS DATE)");
14764            }
14765            // BigQuery uses CAST syntax for type literals
14766            // DATE 'value' -> CAST('value' AS DATE)
14767            Some(DialectType::BigQuery) => {
14768                self.write("CAST('");
14769                self.write(d);
14770                self.write("' AS DATE)");
14771            }
14772            // Exasol uses CAST syntax for DATE literals
14773            // DATE 'value' -> CAST('value' AS DATE)
14774            Some(DialectType::Exasol) => {
14775                self.write("CAST('");
14776                self.write(d);
14777                self.write("' AS DATE)");
14778            }
14779            // Snowflake uses CAST syntax for DATE literals
14780            // DATE 'value' -> CAST('value' AS DATE)
14781            Some(DialectType::Snowflake) => {
14782                self.write("CAST('");
14783                self.write(d);
14784                self.write("' AS DATE)");
14785            }
14786            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14787            Some(DialectType::PostgreSQL)
14788            | Some(DialectType::MySQL)
14789            | Some(DialectType::SingleStore)
14790            | Some(DialectType::TiDB)
14791            | Some(DialectType::Redshift) => {
14792                self.write("CAST('");
14793                self.write(d);
14794                self.write("' AS DATE)");
14795            }
14796            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14797            Some(DialectType::DuckDB)
14798            | Some(DialectType::Presto)
14799            | Some(DialectType::Trino)
14800            | Some(DialectType::Athena)
14801            | Some(DialectType::Spark)
14802            | Some(DialectType::Databricks)
14803            | Some(DialectType::Hive) => {
14804                self.write("CAST('");
14805                self.write(d);
14806                self.write("' AS DATE)");
14807            }
14808            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14809            Some(DialectType::Oracle) => {
14810                self.write("TO_DATE('");
14811                self.write(d);
14812                self.write("', 'YYYY-MM-DD')");
14813            }
14814            // Standard SQL: DATE '...'
14815            _ => {
14816                self.write_keyword("DATE");
14817                self.write(" '");
14818                self.write(d);
14819                self.write("'");
14820            }
14821        }
14822        Ok(())
14823    }
14824
14825    /// Generate a TIME literal with dialect-specific formatting
14826    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14827        use crate::dialects::DialectType;
14828
14829        match self.config.dialect {
14830            // SQL Server uses CONVERT or CAST
14831            Some(DialectType::TSQL) => {
14832                self.write("CAST('");
14833                self.write(t);
14834                self.write("' AS TIME)");
14835            }
14836            // Standard SQL: TIME '...'
14837            _ => {
14838                self.write_keyword("TIME");
14839                self.write(" '");
14840                self.write(t);
14841                self.write("'");
14842            }
14843        }
14844        Ok(())
14845    }
14846
14847    /// Generate a date expression for Dremio, converting DATE literals to CAST
14848    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14849        use crate::expressions::Literal;
14850
14851        match expr {
14852            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14853                let Literal::Date(d) = lit.as_ref() else {
14854                    unreachable!()
14855                };
14856                // DATE 'value' -> CAST('value' AS DATE)
14857                self.write("CAST('");
14858                self.write(d);
14859                self.write("' AS DATE)");
14860            }
14861            _ => {
14862                // For all other expressions, generate normally
14863                self.generate_expression(expr)?;
14864            }
14865        }
14866        Ok(())
14867    }
14868
14869    /// Generate a TIMESTAMP literal with dialect-specific formatting
14870    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14871        use crate::dialects::DialectType;
14872
14873        match self.config.dialect {
14874            // SQL Server uses CONVERT or CAST
14875            Some(DialectType::TSQL) => {
14876                self.write("CAST('");
14877                self.write(ts);
14878                self.write("' AS DATETIME2)");
14879            }
14880            // BigQuery uses CAST syntax for type literals
14881            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14882            Some(DialectType::BigQuery) => {
14883                self.write("CAST('");
14884                self.write(ts);
14885                self.write("' AS TIMESTAMP)");
14886            }
14887            // Snowflake uses CAST syntax for TIMESTAMP literals
14888            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14889            Some(DialectType::Snowflake) => {
14890                self.write("CAST('");
14891                self.write(ts);
14892                self.write("' AS TIMESTAMP)");
14893            }
14894            // Dremio uses CAST syntax for TIMESTAMP literals
14895            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14896            Some(DialectType::Dremio) => {
14897                self.write("CAST('");
14898                self.write(ts);
14899                self.write("' AS TIMESTAMP)");
14900            }
14901            // Exasol uses CAST syntax for TIMESTAMP literals
14902            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14903            Some(DialectType::Exasol) => {
14904                self.write("CAST('");
14905                self.write(ts);
14906                self.write("' AS TIMESTAMP)");
14907            }
14908            // Oracle prefers TO_TIMESTAMP function call
14909            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14910            Some(DialectType::Oracle) => {
14911                self.write("TO_TIMESTAMP('");
14912                self.write(ts);
14913                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14914            }
14915            // Presto/Trino: always use CAST for TIMESTAMP literals
14916            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14917                if Self::timestamp_has_timezone(ts) {
14918                    self.write("CAST('");
14919                    self.write(ts);
14920                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14921                } else {
14922                    self.write("CAST('");
14923                    self.write(ts);
14924                    self.write("' AS TIMESTAMP)");
14925                }
14926            }
14927            // ClickHouse: CAST('...' AS Nullable(DateTime))
14928            Some(DialectType::ClickHouse) => {
14929                self.write("CAST('");
14930                self.write(ts);
14931                self.write("' AS Nullable(DateTime))");
14932            }
14933            // Spark: CAST('...' AS TIMESTAMP)
14934            Some(DialectType::Spark) => {
14935                self.write("CAST('");
14936                self.write(ts);
14937                self.write("' AS TIMESTAMP)");
14938            }
14939            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14940            // but TIMESTAMP '...' for special values like 'epoch'
14941            Some(DialectType::Redshift) => {
14942                if ts == "epoch" {
14943                    self.write_keyword("TIMESTAMP");
14944                    self.write(" '");
14945                    self.write(ts);
14946                    self.write("'");
14947                } else {
14948                    self.write("CAST('");
14949                    self.write(ts);
14950                    self.write("' AS TIMESTAMP)");
14951                }
14952            }
14953            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14954            Some(DialectType::PostgreSQL)
14955            | Some(DialectType::Hive)
14956            | Some(DialectType::SQLite)
14957            | Some(DialectType::DuckDB)
14958            | Some(DialectType::Athena)
14959            | Some(DialectType::Drill)
14960            | Some(DialectType::Teradata) => {
14961                self.write("CAST('");
14962                self.write(ts);
14963                self.write("' AS TIMESTAMP)");
14964            }
14965            // MySQL/StarRocks: CAST('...' AS DATETIME)
14966            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14967                self.write("CAST('");
14968                self.write(ts);
14969                self.write("' AS DATETIME)");
14970            }
14971            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14972            Some(DialectType::Databricks) => {
14973                self.write("CAST('");
14974                self.write(ts);
14975                self.write("' AS TIMESTAMP_NTZ)");
14976            }
14977            // Standard SQL: TIMESTAMP '...'
14978            _ => {
14979                self.write_keyword("TIMESTAMP");
14980                self.write(" '");
14981                self.write(ts);
14982                self.write("'");
14983            }
14984        }
14985        Ok(())
14986    }
14987
14988    /// Check if a timestamp string contains a timezone identifier
14989    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14990    fn timestamp_has_timezone(ts: &str) -> bool {
14991        // Check for common IANA timezone patterns: Continent/City format
14992        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14993        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14994        let ts_lower = ts.to_ascii_lowercase();
14995
14996        // Check for Continent/City pattern (most common)
14997        let continent_prefixes = [
14998            "africa/",
14999            "america/",
15000            "antarctica/",
15001            "arctic/",
15002            "asia/",
15003            "atlantic/",
15004            "australia/",
15005            "europe/",
15006            "indian/",
15007            "pacific/",
15008            "etc/",
15009            "brazil/",
15010            "canada/",
15011            "chile/",
15012            "mexico/",
15013            "us/",
15014        ];
15015
15016        for prefix in &continent_prefixes {
15017            if ts_lower.contains(prefix) {
15018                return true;
15019            }
15020        }
15021
15022        // Check for standalone timezone abbreviations at the end
15023        // These typically appear after the time portion
15024        let tz_abbrevs = [
15025            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15026            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15027            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15028        ];
15029
15030        for abbrev in &tz_abbrevs {
15031            if ts_lower.ends_with(abbrev) {
15032                return true;
15033            }
15034        }
15035
15036        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15037        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15038        // Look for pattern: space followed by + or - and digits (optionally with :)
15039        let trimmed = ts.trim();
15040        if let Some(last_space) = trimmed.rfind(' ') {
15041            let suffix = &trimmed[last_space + 1..];
15042            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15043                // Check if rest is numeric (possibly with : for hh:mm format)
15044                let rest = &suffix[1..];
15045                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15046                    return true;
15047                }
15048            }
15049        }
15050
15051        false
15052    }
15053
15054    /// Generate a DATETIME literal with dialect-specific formatting
15055    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15056        use crate::dialects::DialectType;
15057
15058        match self.config.dialect {
15059            // BigQuery uses CAST syntax for type literals
15060            // DATETIME 'value' -> CAST('value' AS DATETIME)
15061            Some(DialectType::BigQuery) => {
15062                self.write("CAST('");
15063                self.write(dt);
15064                self.write("' AS DATETIME)");
15065            }
15066            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15067            Some(DialectType::DuckDB) => {
15068                self.write("CAST('");
15069                self.write(dt);
15070                self.write("' AS TIMESTAMP)");
15071            }
15072            // DATETIME is primarily a BigQuery type
15073            // Output as DATETIME '...' for dialects that support it
15074            _ => {
15075                self.write_keyword("DATETIME");
15076                self.write(" '");
15077                self.write(dt);
15078                self.write("'");
15079            }
15080        }
15081        Ok(())
15082    }
15083
15084    /// Generate a string literal with dialect-specific escaping
15085    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15086        use crate::dialects::DialectType;
15087
15088        match self.config.dialect {
15089            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15090            // and backslash escaping for special characters like newlines
15091            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15092            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15093                // Hive/Spark use backslash escaping for quotes (\') and special chars
15094                self.write("'");
15095                for c in s.chars() {
15096                    match c {
15097                        '\'' => self.write("\\'"),
15098                        '\\' => self.write("\\\\"),
15099                        '\n' => self.write("\\n"),
15100                        '\r' => self.write("\\r"),
15101                        '\t' => self.write("\\t"),
15102                        '\0' => self.write("\\0"),
15103                        _ => self.output.push(c),
15104                    }
15105                }
15106                self.write("'");
15107            }
15108            Some(DialectType::Drill) => {
15109                // Drill uses SQL-standard quote doubling ('') for quotes,
15110                // but backslash escaping for special characters
15111                self.write("'");
15112                for c in s.chars() {
15113                    match c {
15114                        '\'' => self.write("''"),
15115                        '\\' => self.write("\\\\"),
15116                        '\n' => self.write("\\n"),
15117                        '\r' => self.write("\\r"),
15118                        '\t' => self.write("\\t"),
15119                        '\0' => self.write("\\0"),
15120                        _ => self.output.push(c),
15121                    }
15122                }
15123                self.write("'");
15124            }
15125            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15126                self.write("'");
15127                for c in s.chars() {
15128                    match c {
15129                        // MySQL uses SQL standard quote doubling
15130                        '\'' => self.write("''"),
15131                        '\\' => self.write("\\\\"),
15132                        '\n' => self.write("\\n"),
15133                        '\r' => self.write("\\r"),
15134                        '\t' => self.write("\\t"),
15135                        // sqlglot writes a literal NUL for this case
15136                        '\0' => self.output.push('\0'),
15137                        _ => self.output.push(c),
15138                    }
15139                }
15140                self.write("'");
15141            }
15142            // BigQuery: Uses backslash escaping
15143            Some(DialectType::BigQuery) => {
15144                self.write("'");
15145                for c in s.chars() {
15146                    match c {
15147                        '\'' => self.write("\\'"),
15148                        '\\' => self.write("\\\\"),
15149                        '\n' => self.write("\\n"),
15150                        '\r' => self.write("\\r"),
15151                        '\t' => self.write("\\t"),
15152                        '\0' => self.write("\\0"),
15153                        '\x07' => self.write("\\a"),
15154                        '\x08' => self.write("\\b"),
15155                        '\x0C' => self.write("\\f"),
15156                        '\x0B' => self.write("\\v"),
15157                        _ => self.output.push(c),
15158                    }
15159                }
15160                self.write("'");
15161            }
15162            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15163            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15164            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15165            Some(DialectType::Athena) => {
15166                if self.athena_hive_context {
15167                    // Hive-style: backslash escaping
15168                    self.write("'");
15169                    for c in s.chars() {
15170                        match c {
15171                            '\'' => self.write("\\'"),
15172                            '\\' => self.write("\\\\"),
15173                            '\n' => self.write("\\n"),
15174                            '\r' => self.write("\\r"),
15175                            '\t' => self.write("\\t"),
15176                            '\0' => self.write("\\0"),
15177                            _ => self.output.push(c),
15178                        }
15179                    }
15180                    self.write("'");
15181                } else {
15182                    // Trino-style: SQL-standard escaping, preserve backslashes
15183                    self.write("'");
15184                    for c in s.chars() {
15185                        match c {
15186                            '\'' => self.write("''"),
15187                            // Preserve backslashes literally (no re-escaping)
15188                            _ => self.output.push(c),
15189                        }
15190                    }
15191                    self.write("'");
15192                }
15193            }
15194            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15195            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15196            // becomes string value '\\'), so we should NOT re-escape backslashes.
15197            // We only need to escape single quotes.
15198            Some(DialectType::Snowflake) => {
15199                self.write("'");
15200                for c in s.chars() {
15201                    match c {
15202                        '\'' => self.write("\\'"),
15203                        // Backslashes are already escaped in the tokenized string, don't re-escape
15204                        // Only escape special characters that might not have been escaped
15205                        '\n' => self.write("\\n"),
15206                        '\r' => self.write("\\r"),
15207                        '\t' => self.write("\\t"),
15208                        _ => self.output.push(c),
15209                    }
15210                }
15211                self.write("'");
15212            }
15213            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15214            Some(DialectType::PostgreSQL) => {
15215                self.write("'");
15216                for c in s.chars() {
15217                    match c {
15218                        '\'' => self.write("''"),
15219                        _ => self.output.push(c),
15220                    }
15221                }
15222                self.write("'");
15223            }
15224            // Redshift: Uses backslash escaping for single quotes
15225            Some(DialectType::Redshift) => {
15226                self.write("'");
15227                for c in s.chars() {
15228                    match c {
15229                        '\'' => self.write("\\'"),
15230                        _ => self.output.push(c),
15231                    }
15232                }
15233                self.write("'");
15234            }
15235            // Oracle: Uses standard double single-quote escaping
15236            Some(DialectType::Oracle) => {
15237                self.write("'");
15238                for ch in s.chars() {
15239                    if ch == '\'' {
15240                        self.output.push_str("''");
15241                    } else {
15242                        self.output.push(ch);
15243                    }
15244                }
15245                self.write("'");
15246            }
15247            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15248            // backslash escaping for backslashes and special characters
15249            Some(DialectType::ClickHouse) => {
15250                self.write("'");
15251                for c in s.chars() {
15252                    match c {
15253                        '\'' => self.write("''"),
15254                        '\\' => self.write("\\\\"),
15255                        '\n' => self.write("\\n"),
15256                        '\r' => self.write("\\r"),
15257                        '\t' => self.write("\\t"),
15258                        '\0' => self.write("\\0"),
15259                        '\x07' => self.write("\\a"),
15260                        '\x08' => self.write("\\b"),
15261                        '\x0C' => self.write("\\f"),
15262                        '\x0B' => self.write("\\v"),
15263                        // Non-printable characters: emit as \xNN hex escapes
15264                        c if c.is_control() || (c as u32) < 0x20 => {
15265                            let byte = c as u32;
15266                            if byte < 256 {
15267                                self.write(&format!("\\x{:02X}", byte));
15268                            } else {
15269                                self.output.push(c);
15270                            }
15271                        }
15272                        _ => self.output.push(c),
15273                    }
15274                }
15275                self.write("'");
15276            }
15277            // Default: SQL standard double single quotes (works for most dialects)
15278            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15279            _ => {
15280                self.write("'");
15281                for ch in s.chars() {
15282                    if ch == '\'' {
15283                        self.output.push_str("''");
15284                    } else {
15285                        self.output.push(ch);
15286                    }
15287                }
15288                self.write("'");
15289            }
15290        }
15291        Ok(())
15292    }
15293
15294    /// Write a byte string with proper escaping for BigQuery-style byte literals
15295    /// Escapes characters as \xNN hex escapes where needed
15296    fn write_escaped_byte_string(&mut self, s: &str) {
15297        for c in s.chars() {
15298            match c {
15299                // Escape single quotes
15300                '\'' => self.write("\\'"),
15301                // Escape backslashes
15302                '\\' => self.write("\\\\"),
15303                // Keep all printable characters (including non-ASCII) as-is
15304                _ if !c.is_control() => self.output.push(c),
15305                // Escape control characters as hex
15306                _ => {
15307                    let byte = c as u32;
15308                    if byte < 256 {
15309                        self.write(&format!("\\x{:02x}", byte));
15310                    } else {
15311                        // For unicode characters, write each UTF-8 byte
15312                        for b in c.to_string().as_bytes() {
15313                            self.write(&format!("\\x{:02x}", b));
15314                        }
15315                    }
15316                }
15317            }
15318        }
15319    }
15320
15321    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15322        use crate::dialects::DialectType;
15323
15324        // Different dialects have different boolean literal formats
15325        match self.config.dialect {
15326            // SQL Server typically uses 1/0 for boolean literals in many contexts
15327            // However, TRUE/FALSE also works in modern versions
15328            Some(DialectType::TSQL) => {
15329                self.write(if b.value { "1" } else { "0" });
15330            }
15331            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15332            Some(DialectType::Oracle) => {
15333                self.write(if b.value { "1" } else { "0" });
15334            }
15335            // MySQL accepts TRUE/FALSE as aliases for 1/0
15336            Some(DialectType::MySQL) => {
15337                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15338            }
15339            // Most other dialects support TRUE/FALSE
15340            _ => {
15341                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15342            }
15343        }
15344        Ok(())
15345    }
15346
15347    /// Generate an identifier that's used as an alias name
15348    /// This quotes reserved keywords in addition to already-quoted identifiers
15349    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15350        let name = &id.name;
15351        let quote_style = &self.config.identifier_quote_style;
15352
15353        // For aliases, quote if:
15354        // 1. The identifier was explicitly quoted in the source
15355        // 2. The identifier is a reserved keyword for the current dialect
15356        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15357
15358        // Normalize identifier if configured
15359        let output_name = if self.config.normalize_identifiers && !id.quoted {
15360            name.to_ascii_lowercase()
15361        } else {
15362            name.to_string()
15363        };
15364
15365        if needs_quoting {
15366            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
15367                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15368                && quote_style.start == '"'
15369                && output_name.contains('"')
15370            {
15371                &IdentifierQuoteStyle::BACKTICK
15372            } else {
15373                quote_style
15374            };
15375            // Escape any quote characters within the identifier
15376            let escaped_name = if quote_style.start == quote_style.end {
15377                output_name.replace(
15378                    quote_style.end,
15379                    &format!("{}{}", quote_style.end, quote_style.end),
15380                )
15381            } else {
15382                output_name.replace(
15383                    quote_style.end,
15384                    &format!("{}{}", quote_style.end, quote_style.end),
15385                )
15386            };
15387            self.write(&format!(
15388                "{}{}{}",
15389                quote_style.start, escaped_name, quote_style.end
15390            ));
15391        } else {
15392            self.write(&output_name);
15393        }
15394
15395        // Output trailing comments
15396        for comment in &id.trailing_comments {
15397            self.write(" ");
15398            self.write_formatted_comment(comment);
15399        }
15400        Ok(())
15401    }
15402
15403    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15404        use crate::dialects::DialectType;
15405
15406        let name = &id.name;
15407
15408        // For Athena, use backticks in Hive context, double quotes in Trino context
15409        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15410            && self.athena_hive_context
15411        {
15412            &IdentifierQuoteStyle::BACKTICK
15413        } else {
15414            &self.config.identifier_quote_style
15415        };
15416
15417        // Quote if:
15418        // 1. The identifier was explicitly quoted in the source
15419        // 2. The identifier is a reserved keyword for the current dialect
15420        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15421        // This matches Python sqlglot's identifier_sql behavior
15422        // Also quote identifiers starting with digits if the target dialect doesn't support them
15423        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15424        let needs_digit_quoting = starts_with_digit
15425            && !self.config.identifiers_can_start_with_digit
15426            && self.config.dialect.is_some();
15427        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15428            && name.len() > 2
15429            && (name.starts_with("0x") || name.starts_with("0X"))
15430            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15431        let clickhouse_unsafe_identifier =
15432            matches!(self.config.dialect, Some(DialectType::ClickHouse))
15433                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15434                && !name.starts_with('{')
15435                && !name.contains('(')
15436                && !name.contains(')')
15437                && name != "?"
15438                && name
15439                    .chars()
15440                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
15441        let needs_quoting = id.quoted
15442            || self.is_reserved_keyword(name)
15443            || self.config.always_quote_identifiers
15444            || needs_digit_quoting
15445            || mysql_invalid_hex_identifier
15446            || clickhouse_unsafe_identifier;
15447
15448        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15449        // When quoted, we need to output `name`(16) not `name(16)`
15450        let (base_name, suffix) = if needs_quoting {
15451            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15452            if let Some(paren_pos) = name.find('(') {
15453                let base = &name[..paren_pos];
15454                let rest = &name[paren_pos..];
15455                // Verify it looks like (digits) or (digits) ASC/DESC
15456                if rest.starts_with('(')
15457                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15458                {
15459                    // Check if content between parens is all digits
15460                    let close_paren = rest.find(')').unwrap_or(rest.len());
15461                    let inside = &rest[1..close_paren];
15462                    if inside.chars().all(|c| c.is_ascii_digit()) {
15463                        (base.to_string(), rest.to_string())
15464                    } else {
15465                        (name.to_string(), String::new())
15466                    }
15467                } else {
15468                    (name.to_string(), String::new())
15469                }
15470            } else if name.ends_with(" ASC") {
15471                let base = &name[..name.len() - 4];
15472                (base.to_string(), " ASC".to_string())
15473            } else if name.ends_with(" DESC") {
15474                let base = &name[..name.len() - 5];
15475                (base.to_string(), " DESC".to_string())
15476            } else {
15477                (name.to_string(), String::new())
15478            }
15479        } else {
15480            (name.to_string(), String::new())
15481        };
15482
15483        // Normalize identifier if configured, with special handling for Exasol
15484        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15485        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15486        let output_name = if self.config.normalize_identifiers && !id.quoted {
15487            base_name.to_ascii_lowercase()
15488        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15489            && !id.quoted
15490            && self.is_reserved_keyword(name)
15491        {
15492            // Exasol: uppercase reserved keywords when quoting them
15493            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15494            base_name.to_ascii_uppercase()
15495        } else {
15496            base_name
15497        };
15498
15499        if needs_quoting {
15500            // Escape any quote characters within the identifier
15501            let escaped_name = if quote_style.start == quote_style.end {
15502                // Same start/end char (e.g., " or `) - double the quote char
15503                output_name.replace(
15504                    quote_style.end,
15505                    &format!("{}{}", quote_style.end, quote_style.end),
15506                )
15507            } else {
15508                // Different start/end (e.g., [ and ]) - escape only the end char
15509                output_name.replace(
15510                    quote_style.end,
15511                    &format!("{}{}", quote_style.end, quote_style.end),
15512                )
15513            };
15514            self.write(&format!(
15515                "{}{}{}{}",
15516                quote_style.start, escaped_name, quote_style.end, suffix
15517            ));
15518        } else {
15519            self.write(&output_name);
15520        }
15521
15522        // Output trailing comments
15523        for comment in &id.trailing_comments {
15524            self.write(" ");
15525            self.write_formatted_comment(comment);
15526        }
15527        Ok(())
15528    }
15529
15530    fn generate_column(&mut self, col: &Column) -> Result<()> {
15531        use crate::dialects::DialectType;
15532
15533        if let Some(table) = &col.table {
15534            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15535            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15536            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15537            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15538                && !table.quoted
15539                && table.name.eq_ignore_ascii_case("LOCAL");
15540
15541            if is_exasol_local_prefix {
15542                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15543                self.write("LOCAL");
15544            } else {
15545                self.generate_identifier(table)?;
15546            }
15547            self.write(".");
15548        }
15549        self.generate_identifier(&col.name)?;
15550        // Oracle-style join marker (+)
15551        // Only output if dialect supports it (Oracle, Exasol)
15552        if col.join_mark && self.config.supports_column_join_marks {
15553            self.write(" (+)");
15554        }
15555        // Output trailing comments
15556        for comment in &col.trailing_comments {
15557            self.write_space();
15558            self.write_formatted_comment(comment);
15559        }
15560        Ok(())
15561    }
15562
15563    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15564    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15565    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15566        use crate::dialects::DialectType;
15567        use crate::expressions::PseudocolumnType;
15568
15569        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15570        if pc.kind == PseudocolumnType::Sysdate
15571            && !matches!(
15572                self.config.dialect,
15573                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15574            )
15575        {
15576            self.write_keyword("CURRENT_TIMESTAMP");
15577            // Add () for dialects that expect it
15578            if matches!(
15579                self.config.dialect,
15580                Some(DialectType::MySQL)
15581                    | Some(DialectType::ClickHouse)
15582                    | Some(DialectType::Spark)
15583                    | Some(DialectType::Databricks)
15584                    | Some(DialectType::Hive)
15585            ) {
15586                self.write("()");
15587            }
15588        } else {
15589            self.write(pc.kind.as_str());
15590        }
15591        Ok(())
15592    }
15593
15594    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15595    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15596        use crate::dialects::DialectType;
15597
15598        // Generate native CONNECT BY for Oracle and Snowflake
15599        // For other dialects, add a comment noting manual conversion needed
15600        let supports_connect_by = matches!(
15601            self.config.dialect,
15602            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15603        );
15604
15605        if !supports_connect_by && self.config.dialect.is_some() {
15606            // Add comment for unsupported dialects
15607            if self.config.pretty {
15608                self.write_newline();
15609            } else {
15610                self.write_space();
15611            }
15612            self.write_unsupported_comment(
15613                "CONNECT BY requires manual conversion to recursive CTE",
15614            )?;
15615        }
15616
15617        // Generate START WITH if present (before CONNECT BY)
15618        if let Some(start) = &connect.start {
15619            if self.config.pretty {
15620                self.write_newline();
15621            } else {
15622                self.write_space();
15623            }
15624            self.write_keyword("START WITH");
15625            self.write_space();
15626            self.generate_expression(start)?;
15627        }
15628
15629        // Generate CONNECT BY
15630        if self.config.pretty {
15631            self.write_newline();
15632        } else {
15633            self.write_space();
15634        }
15635        self.write_keyword("CONNECT BY");
15636        if connect.nocycle {
15637            self.write_space();
15638            self.write_keyword("NOCYCLE");
15639        }
15640        self.write_space();
15641        self.generate_expression(&connect.connect)?;
15642
15643        Ok(())
15644    }
15645
15646    /// Generate Connect expression (for Expression::Connect variant)
15647    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15648        self.generate_connect(connect)
15649    }
15650
15651    /// Generate PRIOR expression
15652    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15653        self.write_keyword("PRIOR");
15654        self.write_space();
15655        self.generate_expression(&prior.this)?;
15656        Ok(())
15657    }
15658
15659    /// Generate CONNECT_BY_ROOT function
15660    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15661    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15662        self.write_keyword("CONNECT_BY_ROOT");
15663        self.write_space();
15664        self.generate_expression(&cbr.this)?;
15665        Ok(())
15666    }
15667
15668    /// Generate MATCH_RECOGNIZE clause
15669    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15670        use crate::dialects::DialectType;
15671
15672        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15673        let supports_match_recognize = matches!(
15674            self.config.dialect,
15675            Some(DialectType::Oracle)
15676                | Some(DialectType::Snowflake)
15677                | Some(DialectType::Presto)
15678                | Some(DialectType::Trino)
15679        );
15680
15681        // Generate the source table first
15682        if let Some(source) = &mr.this {
15683            self.generate_expression(source)?;
15684        }
15685
15686        if !supports_match_recognize {
15687            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15688            return Ok(());
15689        }
15690
15691        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15692        if self.config.pretty {
15693            self.write_newline();
15694        } else {
15695            self.write_space();
15696        }
15697
15698        self.write_keyword("MATCH_RECOGNIZE");
15699        self.write(" (");
15700
15701        if self.config.pretty {
15702            self.indent_level += 1;
15703        }
15704
15705        let mut needs_separator = false;
15706
15707        // PARTITION BY
15708        if let Some(partition_by) = &mr.partition_by {
15709            if !partition_by.is_empty() {
15710                if self.config.pretty {
15711                    self.write_newline();
15712                    self.write_indent();
15713                }
15714                self.write_keyword("PARTITION BY");
15715                self.write_space();
15716                for (i, expr) in partition_by.iter().enumerate() {
15717                    if i > 0 {
15718                        self.write(", ");
15719                    }
15720                    self.generate_expression(expr)?;
15721                }
15722                needs_separator = true;
15723            }
15724        }
15725
15726        // ORDER BY
15727        if let Some(order_by) = &mr.order_by {
15728            if !order_by.is_empty() {
15729                if needs_separator {
15730                    if self.config.pretty {
15731                        self.write_newline();
15732                        self.write_indent();
15733                    } else {
15734                        self.write_space();
15735                    }
15736                } else if self.config.pretty {
15737                    self.write_newline();
15738                    self.write_indent();
15739                }
15740                self.write_keyword("ORDER BY");
15741                // In pretty mode, put each ORDER BY column on a new indented line
15742                if self.config.pretty {
15743                    self.indent_level += 1;
15744                    for (i, ordered) in order_by.iter().enumerate() {
15745                        if i > 0 {
15746                            self.write(",");
15747                        }
15748                        self.write_newline();
15749                        self.write_indent();
15750                        self.generate_ordered(ordered)?;
15751                    }
15752                    self.indent_level -= 1;
15753                } else {
15754                    self.write_space();
15755                    for (i, ordered) in order_by.iter().enumerate() {
15756                        if i > 0 {
15757                            self.write(", ");
15758                        }
15759                        self.generate_ordered(ordered)?;
15760                    }
15761                }
15762                needs_separator = true;
15763            }
15764        }
15765
15766        // MEASURES
15767        if let Some(measures) = &mr.measures {
15768            if !measures.is_empty() {
15769                if needs_separator {
15770                    if self.config.pretty {
15771                        self.write_newline();
15772                        self.write_indent();
15773                    } else {
15774                        self.write_space();
15775                    }
15776                } else if self.config.pretty {
15777                    self.write_newline();
15778                    self.write_indent();
15779                }
15780                self.write_keyword("MEASURES");
15781                // In pretty mode, put each MEASURE on a new indented line
15782                if self.config.pretty {
15783                    self.indent_level += 1;
15784                    for (i, measure) in measures.iter().enumerate() {
15785                        if i > 0 {
15786                            self.write(",");
15787                        }
15788                        self.write_newline();
15789                        self.write_indent();
15790                        // Handle RUNNING/FINAL prefix
15791                        if let Some(semantics) = &measure.window_frame {
15792                            match semantics {
15793                                MatchRecognizeSemantics::Running => {
15794                                    self.write_keyword("RUNNING");
15795                                    self.write_space();
15796                                }
15797                                MatchRecognizeSemantics::Final => {
15798                                    self.write_keyword("FINAL");
15799                                    self.write_space();
15800                                }
15801                            }
15802                        }
15803                        self.generate_expression(&measure.this)?;
15804                    }
15805                    self.indent_level -= 1;
15806                } else {
15807                    self.write_space();
15808                    for (i, measure) in measures.iter().enumerate() {
15809                        if i > 0 {
15810                            self.write(", ");
15811                        }
15812                        // Handle RUNNING/FINAL prefix
15813                        if let Some(semantics) = &measure.window_frame {
15814                            match semantics {
15815                                MatchRecognizeSemantics::Running => {
15816                                    self.write_keyword("RUNNING");
15817                                    self.write_space();
15818                                }
15819                                MatchRecognizeSemantics::Final => {
15820                                    self.write_keyword("FINAL");
15821                                    self.write_space();
15822                                }
15823                            }
15824                        }
15825                        self.generate_expression(&measure.this)?;
15826                    }
15827                }
15828                needs_separator = true;
15829            }
15830        }
15831
15832        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15833        if let Some(rows) = &mr.rows {
15834            if needs_separator {
15835                if self.config.pretty {
15836                    self.write_newline();
15837                    self.write_indent();
15838                } else {
15839                    self.write_space();
15840                }
15841            } else if self.config.pretty {
15842                self.write_newline();
15843                self.write_indent();
15844            }
15845            match rows {
15846                MatchRecognizeRows::OneRowPerMatch => {
15847                    self.write_keyword("ONE ROW PER MATCH");
15848                }
15849                MatchRecognizeRows::AllRowsPerMatch => {
15850                    self.write_keyword("ALL ROWS PER MATCH");
15851                }
15852                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15853                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15854                }
15855                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15856                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15857                }
15858                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15859                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15860                }
15861            }
15862            needs_separator = true;
15863        }
15864
15865        // AFTER MATCH SKIP
15866        if let Some(after) = &mr.after {
15867            if needs_separator {
15868                if self.config.pretty {
15869                    self.write_newline();
15870                    self.write_indent();
15871                } else {
15872                    self.write_space();
15873                }
15874            } else if self.config.pretty {
15875                self.write_newline();
15876                self.write_indent();
15877            }
15878            match after {
15879                MatchRecognizeAfter::PastLastRow => {
15880                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15881                }
15882                MatchRecognizeAfter::ToNextRow => {
15883                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15884                }
15885                MatchRecognizeAfter::ToFirst(ident) => {
15886                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15887                    self.write_space();
15888                    self.generate_identifier(ident)?;
15889                }
15890                MatchRecognizeAfter::ToLast(ident) => {
15891                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15892                    self.write_space();
15893                    self.generate_identifier(ident)?;
15894                }
15895            }
15896            needs_separator = true;
15897        }
15898
15899        // PATTERN
15900        if let Some(pattern) = &mr.pattern {
15901            if needs_separator {
15902                if self.config.pretty {
15903                    self.write_newline();
15904                    self.write_indent();
15905                } else {
15906                    self.write_space();
15907                }
15908            } else if self.config.pretty {
15909                self.write_newline();
15910                self.write_indent();
15911            }
15912            self.write_keyword("PATTERN");
15913            self.write_space();
15914            self.write("(");
15915            self.write(pattern);
15916            self.write(")");
15917            needs_separator = true;
15918        }
15919
15920        // DEFINE
15921        if let Some(define) = &mr.define {
15922            if !define.is_empty() {
15923                if needs_separator {
15924                    if self.config.pretty {
15925                        self.write_newline();
15926                        self.write_indent();
15927                    } else {
15928                        self.write_space();
15929                    }
15930                } else if self.config.pretty {
15931                    self.write_newline();
15932                    self.write_indent();
15933                }
15934                self.write_keyword("DEFINE");
15935                // In pretty mode, put each DEFINE on a new indented line
15936                if self.config.pretty {
15937                    self.indent_level += 1;
15938                    for (i, (name, expr)) in define.iter().enumerate() {
15939                        if i > 0 {
15940                            self.write(",");
15941                        }
15942                        self.write_newline();
15943                        self.write_indent();
15944                        self.generate_identifier(name)?;
15945                        self.write(" AS ");
15946                        self.generate_expression(expr)?;
15947                    }
15948                    self.indent_level -= 1;
15949                } else {
15950                    self.write_space();
15951                    for (i, (name, expr)) in define.iter().enumerate() {
15952                        if i > 0 {
15953                            self.write(", ");
15954                        }
15955                        self.generate_identifier(name)?;
15956                        self.write(" AS ");
15957                        self.generate_expression(expr)?;
15958                    }
15959                }
15960            }
15961        }
15962
15963        if self.config.pretty {
15964            self.indent_level -= 1;
15965            self.write_newline();
15966        }
15967        self.write(")");
15968
15969        // Alias - only include AS if it was explicitly present in the input
15970        if let Some(alias) = &mr.alias {
15971            self.write(" ");
15972            if mr.alias_explicit_as {
15973                self.write_keyword("AS");
15974                self.write(" ");
15975            }
15976            self.generate_identifier(alias)?;
15977        }
15978
15979        Ok(())
15980    }
15981
15982    /// Generate a query hint /*+ ... */
15983    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15984        use crate::dialects::DialectType;
15985
15986        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15987        let supports_hints = matches!(
15988            self.config.dialect,
15989            None |  // No dialect = preserve everything
15990            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15991            Some(DialectType::Spark) | Some(DialectType::Hive) |
15992            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15993        );
15994
15995        if !supports_hints || hint.expressions.is_empty() {
15996            return Ok(());
15997        }
15998
15999        // First, expand raw hint text into individual hint strings
16000        // This handles the case where the parser stored multiple hints as a single raw string
16001        let mut hint_strings: Vec<String> = Vec::new();
16002        for expr in &hint.expressions {
16003            match expr {
16004                HintExpression::Raw(text) => {
16005                    // Parse raw hint text into individual hint function calls
16006                    let parsed = self.parse_raw_hint_text(text);
16007                    hint_strings.extend(parsed);
16008                }
16009                _ => {
16010                    hint_strings.push(self.hint_expression_to_string(expr)?);
16011                }
16012            }
16013        }
16014
16015        // In pretty mode with multiple hints, always use multiline format
16016        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16017        // always joins with newlines in pretty mode
16018        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16019
16020        if use_multiline {
16021            // Pretty print with each hint on its own line
16022            self.write(" /*+ ");
16023            for (i, hint_str) in hint_strings.iter().enumerate() {
16024                if i > 0 {
16025                    self.write_newline();
16026                    self.write("  "); // 2-space indent within hint block
16027                }
16028                self.write(hint_str);
16029            }
16030            self.write(" */");
16031        } else {
16032            // Single line format
16033            self.write(" /*+ ");
16034            let sep = match self.config.dialect {
16035                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16036                _ => " ",
16037            };
16038            for (i, hint_str) in hint_strings.iter().enumerate() {
16039                if i > 0 {
16040                    self.write(sep);
16041                }
16042                self.write(hint_str);
16043            }
16044            self.write(" */");
16045        }
16046
16047        Ok(())
16048    }
16049
16050    /// Parse raw hint text into individual hint function calls
16051    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16052    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16053    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16054        let mut results = Vec::new();
16055        let mut chars = text.chars().peekable();
16056        let mut current = String::new();
16057        let mut paren_depth = 0;
16058        let mut has_unparseable_content = false;
16059        let mut position_after_last_function = 0;
16060        let mut char_position = 0;
16061
16062        while let Some(c) = chars.next() {
16063            char_position += c.len_utf8();
16064            match c {
16065                '(' => {
16066                    paren_depth += 1;
16067                    current.push(c);
16068                }
16069                ')' => {
16070                    paren_depth -= 1;
16071                    current.push(c);
16072                    // When we close the outer parenthesis, we've completed a hint function
16073                    if paren_depth == 0 {
16074                        let trimmed = current.trim().to_string();
16075                        if !trimmed.is_empty() {
16076                            // Format this hint for pretty printing if needed
16077                            let formatted = self.format_hint_function(&trimmed);
16078                            results.push(formatted);
16079                        }
16080                        current.clear();
16081                        position_after_last_function = char_position;
16082                    }
16083                }
16084                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16085                    // Space/comma/whitespace outside parentheses - skip
16086                }
16087                _ if paren_depth == 0 => {
16088                    // Character outside parentheses - accumulate for potential hint name
16089                    current.push(c);
16090                }
16091                _ => {
16092                    current.push(c);
16093                }
16094            }
16095        }
16096
16097        // Check if there's remaining text after the last function call
16098        let remaining_text = text[position_after_last_function..].trim();
16099        if !remaining_text.is_empty() {
16100            // Check if it looks like valid hint function names
16101            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16102            // If we see multiple words without parens, it's likely unparseable
16103            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16104            let looks_like_hint_functions = words.iter().all(|word| {
16105                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16106                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16107            });
16108
16109            if !looks_like_hint_functions && words.len() > 1 {
16110                has_unparseable_content = true;
16111            }
16112        }
16113
16114        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16115        if has_unparseable_content {
16116            return vec![text.trim().to_string()];
16117        }
16118
16119        // If we couldn't parse anything, return the original text as a single hint
16120        if results.is_empty() {
16121            results.push(text.trim().to_string());
16122        }
16123
16124        results
16125    }
16126
16127    /// Format a hint function for pretty printing
16128    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16129    fn format_hint_function(&self, hint: &str) -> String {
16130        if !self.config.pretty {
16131            return hint.to_string();
16132        }
16133
16134        // Try to parse NAME(args) pattern
16135        if let Some(paren_pos) = hint.find('(') {
16136            if hint.ends_with(')') {
16137                let name = &hint[..paren_pos];
16138                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16139
16140                // Parse arguments (space-separated for Oracle hints)
16141                let args: Vec<&str> = args_str.split_whitespace().collect();
16142
16143                // Calculate total width of arguments
16144                let total_args_width: usize =
16145                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16146
16147                // If too wide, format on multiple lines
16148                if total_args_width > self.config.max_text_width && !args.is_empty() {
16149                    let mut result = format!("{}(\n", name);
16150                    for arg in &args {
16151                        result.push_str("    "); // 4-space indent for args
16152                        result.push_str(arg);
16153                        result.push('\n');
16154                    }
16155                    result.push_str("  )"); // 2-space indent for closing paren
16156                    return result;
16157                }
16158            }
16159        }
16160
16161        hint.to_string()
16162    }
16163
16164    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16165    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16166        match expr {
16167            HintExpression::Function { name, args } => {
16168                // Generate each argument to a string
16169                let arg_strings: Vec<String> = args
16170                    .iter()
16171                    .map(|arg| {
16172                        let mut gen = Generator::with_arc_config(self.config.clone());
16173                        gen.generate_expression(arg)?;
16174                        Ok(gen.output)
16175                    })
16176                    .collect::<Result<Vec<_>>>()?;
16177
16178                // Oracle hints use space-separated arguments, not comma-separated
16179                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16180                    + arg_strings.len().saturating_sub(1); // spaces between args
16181
16182                // Check if function args need multiline formatting
16183                // Use too_wide check for argument formatting
16184                let args_multiline =
16185                    self.config.pretty && total_args_width > self.config.max_text_width;
16186
16187                if args_multiline && !arg_strings.is_empty() {
16188                    // Multiline format for long argument lists
16189                    let mut result = format!("{}(\n", name);
16190                    for arg_str in &arg_strings {
16191                        result.push_str("    "); // 4-space indent for args
16192                        result.push_str(arg_str);
16193                        result.push('\n');
16194                    }
16195                    result.push_str("  )"); // 2-space indent for closing paren
16196                    Ok(result)
16197                } else {
16198                    // Single line format with space-separated args (Oracle style)
16199                    let args_str = arg_strings.join(" ");
16200                    Ok(format!("{}({})", name, args_str))
16201                }
16202            }
16203            HintExpression::Identifier(name) => Ok(name.clone()),
16204            HintExpression::Raw(text) => {
16205                // For pretty printing, try to format the raw text
16206                if self.config.pretty {
16207                    Ok(self.format_hint_function(text))
16208                } else {
16209                    Ok(text.clone())
16210                }
16211            }
16212        }
16213    }
16214
16215    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16216        // PostgreSQL ONLY modifier: prevents scanning child tables
16217        if table.only {
16218            self.write_keyword("ONLY");
16219            self.write_space();
16220        }
16221
16222        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16223        if let Some(ref identifier_func) = table.identifier_func {
16224            self.generate_expression(identifier_func)?;
16225            // If table name parts are present, emit .catalog.schema.name after the function
16226            if !table.name.name.is_empty() {
16227                if let Some(catalog) = &table.catalog {
16228                    self.write(".");
16229                    self.generate_identifier(catalog)?;
16230                }
16231                if let Some(schema) = &table.schema {
16232                    self.write(".");
16233                    self.generate_identifier(schema)?;
16234                }
16235                self.write(".");
16236                self.generate_identifier(&table.name)?;
16237            }
16238        } else {
16239            if let Some(catalog) = &table.catalog {
16240                self.generate_identifier(catalog)?;
16241                self.write(".");
16242            }
16243            if let Some(schema) = &table.schema {
16244                self.generate_identifier(schema)?;
16245                self.write(".");
16246            }
16247            self.generate_identifier(&table.name)?;
16248        }
16249
16250        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16251        if let Some(changes) = &table.changes {
16252            self.write(" ");
16253            self.generate_changes(changes)?;
16254        }
16255
16256        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16257        if !table.partitions.is_empty() {
16258            self.write_space();
16259            self.write_keyword("PARTITION");
16260            self.write("(");
16261            for (i, partition) in table.partitions.iter().enumerate() {
16262                if i > 0 {
16263                    self.write(", ");
16264                }
16265                self.generate_identifier(partition)?;
16266            }
16267            self.write(")");
16268        }
16269
16270        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16271        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16272        if table.changes.is_none() {
16273            if let Some(when) = &table.when {
16274                self.write_space();
16275                self.generate_historical_data(when)?;
16276            }
16277        }
16278
16279        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16280        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16281        if !system_time_post_alias {
16282            if let Some(ref system_time) = table.system_time {
16283                self.write_space();
16284                self.write(system_time);
16285            }
16286        }
16287
16288        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16289        if let Some(ref version) = table.version {
16290            self.write_space();
16291            self.generate_version(version)?;
16292        }
16293
16294        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16295        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16296        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16297        let alias_post_tablesample = self.config.alias_post_tablesample;
16298
16299        if alias_post_tablesample {
16300            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16301            self.generate_table_sample_clause(table)?;
16302        }
16303
16304        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16305        // For SQLite, INDEXED BY hints come after the alias, so skip here
16306        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16307            && table.hints.iter().any(|h| {
16308                if let Expression::Identifier(id) = h {
16309                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16310                } else {
16311                    false
16312                }
16313            });
16314        if !table.hints.is_empty() && !is_sqlite_hint {
16315            for hint in &table.hints {
16316                self.write_space();
16317                self.generate_expression(hint)?;
16318            }
16319        }
16320
16321        if let Some(alias) = &table.alias {
16322            self.write_space();
16323            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16324            // Generic mode and most dialects always use AS for table aliases
16325            let always_use_as = self.config.dialect.is_none()
16326                || matches!(
16327                    self.config.dialect,
16328                    Some(DialectType::Generic)
16329                        | Some(DialectType::PostgreSQL)
16330                        | Some(DialectType::Redshift)
16331                        | Some(DialectType::Snowflake)
16332                        | Some(DialectType::BigQuery)
16333                        | Some(DialectType::DuckDB)
16334                        | Some(DialectType::Presto)
16335                        | Some(DialectType::Trino)
16336                        | Some(DialectType::TSQL)
16337                        | Some(DialectType::Fabric)
16338                        | Some(DialectType::MySQL)
16339                        | Some(DialectType::Spark)
16340                        | Some(DialectType::Hive)
16341                        | Some(DialectType::SQLite)
16342                        | Some(DialectType::Drill)
16343                );
16344            let is_stage_ref = table.name.name.starts_with('@');
16345            // Oracle never uses AS for table aliases
16346            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16347            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16348                self.write_keyword("AS");
16349                self.write_space();
16350            }
16351            self.generate_identifier(alias)?;
16352
16353            // Output column aliases if present: AS t(c1, c2)
16354            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16355            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16356                self.write("(");
16357                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16358                    if i > 0 {
16359                        self.write(", ");
16360                    }
16361                    self.generate_identifier(col_alias)?;
16362                }
16363                self.write(")");
16364            }
16365        }
16366
16367        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16368        if system_time_post_alias {
16369            if let Some(ref system_time) = table.system_time {
16370                self.write_space();
16371                self.write(system_time);
16372            }
16373        }
16374
16375        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16376        if !alias_post_tablesample {
16377            self.generate_table_sample_clause(table)?;
16378        }
16379
16380        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16381        if is_sqlite_hint {
16382            for hint in &table.hints {
16383                self.write_space();
16384                self.generate_expression(hint)?;
16385            }
16386        }
16387
16388        // ClickHouse FINAL modifier
16389        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16390            self.write_space();
16391            self.write_keyword("FINAL");
16392        }
16393
16394        // Output trailing comments
16395        for comment in &table.trailing_comments {
16396            self.write_space();
16397            self.write_formatted_comment(comment);
16398        }
16399        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16400        // output here - they are output by the FROM/PIVOT generator after the full expression
16401
16402        Ok(())
16403    }
16404
16405    /// Helper to output TABLESAMPLE clause for a table reference
16406    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16407        if let Some(ref ts) = table.table_sample {
16408            self.write_space();
16409            if ts.is_using_sample {
16410                self.write_keyword("USING SAMPLE");
16411            } else {
16412                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16413                self.write_keyword(self.config.tablesample_keywords);
16414            }
16415            self.generate_sample_body(ts)?;
16416            // Seed for table-level sample - use dialect's configured keyword
16417            if let Some(ref seed) = ts.seed {
16418                self.write_space();
16419                self.write_keyword(self.config.tablesample_seed_keyword);
16420                self.write(" (");
16421                self.generate_expression(seed)?;
16422                self.write(")");
16423            }
16424        }
16425        Ok(())
16426    }
16427
16428    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16429        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16430        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16431
16432        if sr.quoted {
16433            self.write("'");
16434        }
16435
16436        self.write(&sr.name);
16437        if let Some(path) = &sr.path {
16438            self.write(path);
16439        }
16440
16441        if sr.quoted {
16442            self.write("'");
16443        }
16444
16445        // Output FILE_FORMAT and PATTERN if present
16446        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16447        if has_options {
16448            self.write(" (");
16449            let mut first = true;
16450
16451            if let Some(file_format) = &sr.file_format {
16452                if !first {
16453                    self.write(", ");
16454                }
16455                self.write_keyword("FILE_FORMAT");
16456                self.write(" => ");
16457                self.generate_expression(file_format)?;
16458                first = false;
16459            }
16460
16461            if let Some(pattern) = &sr.pattern {
16462                if !first {
16463                    self.write(", ");
16464                }
16465                self.write_keyword("PATTERN");
16466                self.write(" => '");
16467                self.write(pattern);
16468                self.write("'");
16469            }
16470
16471            self.write(")");
16472        }
16473        Ok(())
16474    }
16475
16476    fn generate_star(&mut self, star: &Star) -> Result<()> {
16477        use crate::dialects::DialectType;
16478
16479        if let Some(table) = &star.table {
16480            self.generate_identifier(table)?;
16481            self.write(".");
16482        }
16483        self.write("*");
16484
16485        // Generate EXCLUDE/EXCEPT clause based on dialect
16486        if let Some(except) = &star.except {
16487            if !except.is_empty() {
16488                self.write_space();
16489                // Use dialect-appropriate keyword
16490                match self.config.dialect {
16491                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16492                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16493                        self.write_keyword("EXCLUDE")
16494                    }
16495                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16496                }
16497                self.write(" (");
16498                for (i, col) in except.iter().enumerate() {
16499                    if i > 0 {
16500                        self.write(", ");
16501                    }
16502                    self.generate_identifier(col)?;
16503                }
16504                self.write(")");
16505            }
16506        }
16507
16508        // Generate REPLACE clause
16509        if let Some(replace) = &star.replace {
16510            if !replace.is_empty() {
16511                self.write_space();
16512                self.write_keyword("REPLACE");
16513                self.write(" (");
16514                for (i, alias) in replace.iter().enumerate() {
16515                    if i > 0 {
16516                        self.write(", ");
16517                    }
16518                    self.generate_expression(&alias.this)?;
16519                    self.write_space();
16520                    self.write_keyword("AS");
16521                    self.write_space();
16522                    self.generate_identifier(&alias.alias)?;
16523                }
16524                self.write(")");
16525            }
16526        }
16527
16528        // Generate RENAME clause (Snowflake specific)
16529        if let Some(rename) = &star.rename {
16530            if !rename.is_empty() {
16531                self.write_space();
16532                self.write_keyword("RENAME");
16533                self.write(" (");
16534                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16535                    if i > 0 {
16536                        self.write(", ");
16537                    }
16538                    self.generate_identifier(old_name)?;
16539                    self.write_space();
16540                    self.write_keyword("AS");
16541                    self.write_space();
16542                    self.generate_identifier(new_name)?;
16543                }
16544                self.write(")");
16545            }
16546        }
16547
16548        // Output trailing comments
16549        for comment in &star.trailing_comments {
16550            self.write_space();
16551            self.write_formatted_comment(comment);
16552        }
16553
16554        Ok(())
16555    }
16556
16557    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16558    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16559        self.write("{");
16560        match expr {
16561            Expression::Star(star) => {
16562                // Generate the star (table.* or just * with optional EXCLUDE)
16563                self.generate_star(star)?;
16564            }
16565            Expression::ILike(ilike) => {
16566                // {* ILIKE 'pattern'} syntax
16567                self.generate_expression(&ilike.left)?;
16568                self.write_space();
16569                self.write_keyword("ILIKE");
16570                self.write_space();
16571                self.generate_expression(&ilike.right)?;
16572            }
16573            _ => {
16574                self.generate_expression(expr)?;
16575            }
16576        }
16577        self.write("}");
16578        Ok(())
16579    }
16580
16581    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16582        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16583        // to avoid duplication (comments are captured as both Column.trailing_comments
16584        // and Alias.pre_alias_comments during parsing)
16585        match &alias.this {
16586            Expression::Column(col) => {
16587                // Generate column without trailing comments - they're in pre_alias_comments
16588                if let Some(table) = &col.table {
16589                    self.generate_identifier(table)?;
16590                    self.write(".");
16591                }
16592                self.generate_identifier(&col.name)?;
16593            }
16594            _ => {
16595                self.generate_expression(&alias.this)?;
16596            }
16597        }
16598
16599        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16600        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16601        // keep pre-alias comments in their original position (between expression and AS).
16602        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16603            for comment in &alias.pre_alias_comments {
16604                self.write_space();
16605                self.write_formatted_comment(comment);
16606            }
16607        }
16608
16609        use crate::dialects::DialectType;
16610
16611        // Determine if we should skip AS keyword for table-valued function aliases
16612        // Oracle and some other dialects don't use AS for table aliases
16613        // Note: We specifically use TableFromRows here, NOT Function, because Function
16614        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16615        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16616        let is_table_source = matches!(
16617            &alias.this,
16618            Expression::JSONTable(_)
16619                | Expression::XMLTable(_)
16620                | Expression::TableFromRows(_)
16621                | Expression::Unnest(_)
16622                | Expression::MatchRecognize(_)
16623                | Expression::Select(_)
16624                | Expression::Subquery(_)
16625                | Expression::Paren(_)
16626        );
16627        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16628        let skip_as = is_table_source && dialect_skips_table_alias_as;
16629
16630        self.write_space();
16631        if !skip_as {
16632            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16633                if let Some(ref alias_keyword) = alias.alias_keyword {
16634                    self.write(alias_keyword);
16635                } else {
16636                    self.write_keyword("AS");
16637                }
16638            } else {
16639                self.write_keyword("AS");
16640            }
16641            self.write_space();
16642        }
16643
16644        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16645        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16646
16647        // Check if we have column aliases only (no table alias name)
16648        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16649            // Generate AS (col1, col2, ...)
16650            self.write("(");
16651            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16652                if i > 0 {
16653                    self.write(", ");
16654                }
16655                self.generate_alias_identifier(col_alias)?;
16656            }
16657            self.write(")");
16658        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16659            // Generate AS alias(col1, col2, ...)
16660            self.generate_alias_identifier(&alias.alias)?;
16661            self.write("(");
16662            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16663                if i > 0 {
16664                    self.write(", ");
16665                }
16666                self.generate_alias_identifier(col_alias)?;
16667            }
16668            self.write(")");
16669        } else {
16670            // Simple alias (or BigQuery without column aliases)
16671            self.generate_alias_identifier(&alias.alias)?;
16672        }
16673
16674        // Output trailing comments (comments after the alias)
16675        for comment in &alias.trailing_comments {
16676            self.write_space();
16677            self.write_formatted_comment(comment);
16678        }
16679
16680        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16681        // moves pre-alias comments to after the alias. When there are trailing_comments,
16682        // the pre-alias comments were already lost (consumed as column trailing comments
16683        // that were then used as pre_alias_comments). We always emit them after alias.
16684        if alias.trailing_comments.is_empty() {
16685            for comment in &alias.pre_alias_comments {
16686                self.write_space();
16687                self.write_formatted_comment(comment);
16688            }
16689        }
16690
16691        Ok(())
16692    }
16693
16694    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16695        use crate::dialects::DialectType;
16696
16697        // SingleStore uses :> syntax
16698        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16699            self.generate_expression(&cast.this)?;
16700            self.write(" :> ");
16701            self.generate_data_type(&cast.to)?;
16702            return Ok(());
16703        }
16704
16705        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16706        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16707            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16708                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16709            if is_unknown_type {
16710                if let Some(format) = &cast.format {
16711                    self.write_keyword("CAST");
16712                    self.write("(");
16713                    self.generate_expression(&cast.this)?;
16714                    self.write_space();
16715                    self.write_keyword("AS");
16716                    self.write_space();
16717                    self.write_keyword("FORMAT");
16718                    self.write_space();
16719                    self.generate_expression(format)?;
16720                    self.write(")");
16721                    return Ok(());
16722                }
16723            }
16724        }
16725
16726        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16727        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16728        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16729            if let Some(format) = &cast.format {
16730                // Check if target type is DATE or TIMESTAMP
16731                let is_date = matches!(cast.to, DataType::Date);
16732                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16733
16734                if is_date || is_timestamp {
16735                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16736                    self.write_keyword(func_name);
16737                    self.write("(");
16738                    self.generate_expression(&cast.this)?;
16739                    self.write(", ");
16740
16741                    // Normalize format string for Oracle (HH -> HH12)
16742                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16743                    if let Expression::Literal(lit) = format.as_ref() {
16744                        if let Literal::String(fmt_str) = lit.as_ref() {
16745                            let normalized = self.normalize_oracle_format(fmt_str);
16746                            self.write("'");
16747                            self.write(&normalized);
16748                            self.write("'");
16749                        }
16750                    } else {
16751                        self.generate_expression(format)?;
16752                    }
16753
16754                    self.write(")");
16755                    return Ok(());
16756                }
16757            }
16758        }
16759
16760        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16761        // This preserves sqlglot's typed inline array literal output.
16762        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16763            if let Expression::Array(arr) = &cast.this {
16764                self.generate_data_type(&cast.to)?;
16765                // Output just the bracket content [values] without the ARRAY prefix
16766                self.write("[");
16767                for (i, expr) in arr.expressions.iter().enumerate() {
16768                    if i > 0 {
16769                        self.write(", ");
16770                    }
16771                    self.generate_expression(expr)?;
16772                }
16773                self.write("]");
16774                return Ok(());
16775            }
16776            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16777                self.generate_data_type(&cast.to)?;
16778                self.generate_expression(&cast.this)?;
16779                return Ok(());
16780            }
16781        }
16782
16783        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16784        // convert the inner Struct to ROW(values...) format
16785        if matches!(
16786            self.config.dialect,
16787            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16788        ) {
16789            if let Expression::Struct(ref s) = cast.this {
16790                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16791                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16792                    self.write_keyword("CAST");
16793                    self.write("(");
16794                    self.generate_struct_as_row(s)?;
16795                    self.write_space();
16796                    self.write_keyword("AS");
16797                    self.write_space();
16798                    self.generate_data_type(&cast.to)?;
16799                    self.write(")");
16800                    return Ok(());
16801                }
16802            }
16803        }
16804
16805        // Determine if we should use :: syntax based on dialect
16806        // PostgreSQL prefers :: for identity, most others prefer CAST()
16807        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16808
16809        if use_double_colon {
16810            // PostgreSQL :: syntax: expr::type
16811            self.generate_expression(&cast.this)?;
16812            self.write("::");
16813            self.generate_data_type(&cast.to)?;
16814        } else {
16815            // Standard CAST() syntax
16816            self.write_keyword("CAST");
16817            self.write("(");
16818            self.generate_expression(&cast.this)?;
16819            self.write_space();
16820            self.write_keyword("AS");
16821            self.write_space();
16822            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16823            // This matches Python sqlglot's CAST_MAPPING behavior
16824            if matches!(
16825                self.config.dialect,
16826                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16827            ) {
16828                match &cast.to {
16829                    DataType::Custom { ref name } => {
16830                        if name.eq_ignore_ascii_case("LONGTEXT")
16831                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16832                            || name.eq_ignore_ascii_case("TINYTEXT")
16833                            || name.eq_ignore_ascii_case("LONGBLOB")
16834                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16835                            || name.eq_ignore_ascii_case("TINYBLOB")
16836                        {
16837                            self.write_keyword("CHAR");
16838                        } else {
16839                            self.generate_data_type(&cast.to)?;
16840                        }
16841                    }
16842                    DataType::VarChar { length, .. } => {
16843                        // MySQL CAST: VARCHAR -> CHAR
16844                        self.write_keyword("CHAR");
16845                        if let Some(n) = length {
16846                            self.write(&format!("({})", n));
16847                        }
16848                    }
16849                    DataType::Text => {
16850                        // MySQL CAST: TEXT -> CHAR
16851                        self.write_keyword("CHAR");
16852                    }
16853                    DataType::Timestamp {
16854                        precision,
16855                        timezone: false,
16856                    } => {
16857                        // MySQL CAST: TIMESTAMP -> DATETIME
16858                        self.write_keyword("DATETIME");
16859                        if let Some(p) = precision {
16860                            self.write(&format!("({})", p));
16861                        }
16862                    }
16863                    _ => {
16864                        self.generate_data_type(&cast.to)?;
16865                    }
16866                }
16867            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16868                // Snowflake CAST: STRING -> VARCHAR
16869                match &cast.to {
16870                    DataType::String { length } => {
16871                        self.write_keyword("VARCHAR");
16872                        if let Some(n) = length {
16873                            self.write(&format!("({})", n));
16874                        }
16875                    }
16876                    _ => {
16877                        self.generate_data_type(&cast.to)?;
16878                    }
16879                }
16880            } else {
16881                self.generate_data_type(&cast.to)?;
16882            }
16883
16884            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16885            if let Some(default) = &cast.default {
16886                self.write_space();
16887                self.write_keyword("DEFAULT");
16888                self.write_space();
16889                self.generate_expression(default)?;
16890                self.write_space();
16891                self.write_keyword("ON");
16892                self.write_space();
16893                self.write_keyword("CONVERSION");
16894                self.write_space();
16895                self.write_keyword("ERROR");
16896            }
16897
16898            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16899            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16900            if let Some(format) = &cast.format {
16901                // Check if Oracle dialect - use comma syntax
16902                if matches!(
16903                    self.config.dialect,
16904                    Some(crate::dialects::DialectType::Oracle)
16905                ) {
16906                    self.write(", ");
16907                } else {
16908                    self.write_space();
16909                    self.write_keyword("FORMAT");
16910                    self.write_space();
16911                }
16912                self.generate_expression(format)?;
16913            }
16914
16915            self.write(")");
16916            // Output trailing comments
16917            for comment in &cast.trailing_comments {
16918                self.write_space();
16919                self.write_formatted_comment(comment);
16920            }
16921        }
16922        Ok(())
16923    }
16924
16925    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16926    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16927    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16928        self.write_keyword("ROW");
16929        self.write("(");
16930        for (i, (_, expr)) in s.fields.iter().enumerate() {
16931            if i > 0 {
16932                self.write(", ");
16933            }
16934            // Recursively convert inner Struct to ROW format
16935            if let Expression::Struct(ref inner_s) = expr {
16936                self.generate_struct_as_row(inner_s)?;
16937            } else {
16938                self.generate_expression(expr)?;
16939            }
16940        }
16941        self.write(")");
16942        Ok(())
16943    }
16944
16945    /// Normalize Oracle date/time format strings
16946    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16947    fn normalize_oracle_format(&self, format: &str) -> String {
16948        // Replace standalone HH with HH12 (but not HH12 or HH24)
16949        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16950        let mut result = String::new();
16951        let chars: Vec<char> = format.chars().collect();
16952        let mut i = 0;
16953
16954        while i < chars.len() {
16955            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16956                // Check what follows HH
16957                if i + 2 < chars.len() {
16958                    let next = chars[i + 2];
16959                    if next == '1' || next == '2' {
16960                        // This is HH12 or HH24, keep as is
16961                        result.push('H');
16962                        result.push('H');
16963                        i += 2;
16964                        continue;
16965                    }
16966                }
16967                // Standalone HH -> HH12
16968                result.push_str("HH12");
16969                i += 2;
16970            } else {
16971                result.push(chars[i]);
16972                i += 1;
16973            }
16974        }
16975
16976        result
16977    }
16978
16979    /// Check if the current dialect prefers :: cast syntax
16980    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
16981    fn dialect_prefers_double_colon(&self) -> bool {
16982        matches!(self.config.dialect, Some(DialectType::ClickHouse))
16983    }
16984
16985    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16986    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16987        use crate::dialects::DialectType;
16988
16989        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16990        let use_percent_operator = matches!(
16991            self.config.dialect,
16992            Some(DialectType::Snowflake)
16993                | Some(DialectType::MySQL)
16994                | Some(DialectType::Presto)
16995                | Some(DialectType::Trino)
16996                | Some(DialectType::PostgreSQL)
16997                | Some(DialectType::DuckDB)
16998                | Some(DialectType::Hive)
16999                | Some(DialectType::Spark)
17000                | Some(DialectType::Databricks)
17001                | Some(DialectType::Athena)
17002        );
17003
17004        if use_percent_operator {
17005            // Wrap complex expressions in parens to preserve precedence
17006            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
17007            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
17008            if needs_paren(&f.this) {
17009                self.write("(");
17010                self.generate_expression(&f.this)?;
17011                self.write(")");
17012            } else {
17013                self.generate_expression(&f.this)?;
17014            }
17015            self.write(" % ");
17016            if needs_paren(&f.expression) {
17017                self.write("(");
17018                self.generate_expression(&f.expression)?;
17019                self.write(")");
17020            } else {
17021                self.generate_expression(&f.expression)?;
17022            }
17023            Ok(())
17024        } else {
17025            self.generate_binary_func("MOD", &f.this, &f.expression)
17026        }
17027    }
17028
17029    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17030    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17031        use crate::dialects::DialectType;
17032
17033        // Snowflake normalizes IFNULL to COALESCE
17034        let func_name = match self.config.dialect {
17035            Some(DialectType::Snowflake) => "COALESCE",
17036            _ => "IFNULL",
17037        };
17038
17039        self.generate_binary_func(func_name, &f.this, &f.expression)
17040    }
17041
17042    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17043    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17044        // Use original function name if preserved (for identity tests)
17045        if let Some(ref original_name) = f.original_name {
17046            return self.generate_binary_func(original_name, &f.this, &f.expression);
17047        }
17048
17049        // Otherwise, use dialect-specific function names
17050        use crate::dialects::DialectType;
17051        let func_name = match self.config.dialect {
17052            Some(DialectType::Snowflake)
17053            | Some(DialectType::ClickHouse)
17054            | Some(DialectType::PostgreSQL)
17055            | Some(DialectType::Presto)
17056            | Some(DialectType::Trino)
17057            | Some(DialectType::Athena)
17058            | Some(DialectType::DuckDB)
17059            | Some(DialectType::BigQuery)
17060            | Some(DialectType::Spark)
17061            | Some(DialectType::Databricks)
17062            | Some(DialectType::Hive) => "COALESCE",
17063            Some(DialectType::MySQL)
17064            | Some(DialectType::Doris)
17065            | Some(DialectType::StarRocks)
17066            | Some(DialectType::SingleStore)
17067            | Some(DialectType::TiDB) => "IFNULL",
17068            _ => "NVL",
17069        };
17070
17071        self.generate_binary_func(func_name, &f.this, &f.expression)
17072    }
17073
17074    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17075    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17076        use crate::dialects::DialectType;
17077
17078        // Snowflake normalizes STDDEV_SAMP to STDDEV
17079        let func_name = match self.config.dialect {
17080            Some(DialectType::Snowflake) => "STDDEV",
17081            _ => "STDDEV_SAMP",
17082        };
17083
17084        self.generate_agg_func(func_name, f)
17085    }
17086
17087    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17088        self.generate_expression(&coll.this)?;
17089        self.write_space();
17090        self.write_keyword("COLLATE");
17091        self.write_space();
17092        if coll.quoted {
17093            // Single-quoted string: COLLATE 'de_DE'
17094            self.write("'");
17095            self.write(&coll.collation);
17096            self.write("'");
17097        } else if coll.double_quoted {
17098            // Double-quoted identifier: COLLATE "de_DE"
17099            self.write("\"");
17100            self.write(&coll.collation);
17101            self.write("\"");
17102        } else {
17103            // Unquoted identifier: COLLATE de_DE
17104            self.write(&coll.collation);
17105        }
17106        Ok(())
17107    }
17108
17109    fn generate_case(&mut self, case: &Case) -> Result<()> {
17110        // In pretty mode, decide whether to expand based on total text width
17111        let multiline_case = if self.config.pretty {
17112            // Build the flat representation to check width
17113            let mut statements: Vec<String> = Vec::new();
17114            let operand_str = if let Some(operand) = &case.operand {
17115                let s = self.generate_to_string(operand)?;
17116                statements.push(format!("CASE {}", s));
17117                s
17118            } else {
17119                statements.push("CASE".to_string());
17120                String::new()
17121            };
17122            let _ = operand_str;
17123            for (condition, result) in &case.whens {
17124                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17125                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17126            }
17127            if let Some(else_) = &case.else_ {
17128                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17129            }
17130            statements.push("END".to_string());
17131            self.too_wide(&statements)
17132        } else {
17133            false
17134        };
17135
17136        self.write_keyword("CASE");
17137        if let Some(operand) = &case.operand {
17138            self.write_space();
17139            self.generate_expression(operand)?;
17140        }
17141        if multiline_case {
17142            self.indent_level += 1;
17143        }
17144        for (condition, result) in &case.whens {
17145            if multiline_case {
17146                self.write_newline();
17147                self.write_indent();
17148            } else {
17149                self.write_space();
17150            }
17151            self.write_keyword("WHEN");
17152            self.write_space();
17153            self.generate_expression(condition)?;
17154            if multiline_case {
17155                self.write_newline();
17156                self.write_indent();
17157            } else {
17158                self.write_space();
17159            }
17160            self.write_keyword("THEN");
17161            self.write_space();
17162            self.generate_expression(result)?;
17163        }
17164        if let Some(else_) = &case.else_ {
17165            if multiline_case {
17166                self.write_newline();
17167                self.write_indent();
17168            } else {
17169                self.write_space();
17170            }
17171            self.write_keyword("ELSE");
17172            self.write_space();
17173            self.generate_expression(else_)?;
17174        }
17175        if multiline_case {
17176            self.indent_level -= 1;
17177            self.write_newline();
17178            self.write_indent();
17179        } else {
17180            self.write_space();
17181        }
17182        self.write_keyword("END");
17183        // Emit any comments that were attached to the CASE keyword
17184        for comment in &case.comments {
17185            self.write(" ");
17186            self.write_formatted_comment(comment);
17187        }
17188        Ok(())
17189    }
17190
17191    fn generate_function(&mut self, func: &Function) -> Result<()> {
17192        // Normalize function name based on dialect settings
17193        let normalized_name = self.normalize_func_name(&func.name);
17194
17195        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17196        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17197            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17198        {
17199            self.write("LIST_FILTER(");
17200            self.write("[");
17201            for (i, arg) in func.args.iter().enumerate() {
17202                if i > 0 {
17203                    self.write(", ");
17204                }
17205                self.generate_expression(arg)?;
17206            }
17207            self.write("], _u -> NOT _u IS NULL)");
17208            return Ok(());
17209        }
17210
17211        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17212        // rather than bracket-array syntax.
17213        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17214            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17215            && func.args.len() == 1
17216        {
17217            let array_expressions = match &func.args[0] {
17218                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17219                Expression::Array(arr) => Some(&arr.expressions),
17220                _ => None,
17221            };
17222            if let Some(expressions) = array_expressions {
17223                self.write_keyword("TO_VARIANT");
17224                self.write("(");
17225                self.write_keyword("ARRAY_CONSTRUCT");
17226                self.write("(");
17227                for (i, arg) in expressions.iter().enumerate() {
17228                    if i > 0 {
17229                        self.write(", ");
17230                    }
17231                    self.generate_expression(arg)?;
17232                }
17233                self.write(")");
17234                self.write(")");
17235                return Ok(());
17236            }
17237        }
17238
17239        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17240        if func.name.eq_ignore_ascii_case("STRUCT")
17241            && !matches!(
17242                self.config.dialect,
17243                Some(DialectType::BigQuery)
17244                    | Some(DialectType::Spark)
17245                    | Some(DialectType::Databricks)
17246                    | Some(DialectType::Hive)
17247                    | None
17248            )
17249        {
17250            return self.generate_struct_function_cross_dialect(func);
17251        }
17252
17253        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17254        // This is an internal marker function for ::? JSON path syntax
17255        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17256            self.generate_expression(&func.args[0])?;
17257            self.write("::?");
17258            // Extract the key from the string literal
17259            if let Expression::Literal(lit) = &func.args[1] {
17260                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17261                    self.write(key);
17262                }
17263            } else {
17264                self.generate_expression(&func.args[1])?;
17265            }
17266            return Ok(());
17267        }
17268
17269        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17270        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17271            self.generate_expression(&func.args[0])?;
17272            self.write(" # ");
17273            self.generate_expression(&func.args[1])?;
17274            return Ok(());
17275        }
17276
17277        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17278        if matches!(
17279            self.config.dialect,
17280            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17281        ) && func.name.eq_ignore_ascii_case("TRY")
17282            && func.args.len() == 1
17283        {
17284            self.generate_expression(&func.args[0])?;
17285            return Ok(());
17286        }
17287
17288        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17289        if self.config.dialect == Some(DialectType::ClickHouse)
17290            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17291            && func.args.len() == 1
17292        {
17293            self.write("dateTrunc('DAY', ");
17294            self.generate_expression(&func.args[0])?;
17295            self.write(")");
17296            return Ok(());
17297        }
17298
17299        // ClickHouse uses dateTrunc casing.
17300        if self.config.dialect == Some(DialectType::ClickHouse)
17301            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17302            && func.args.len() == 2
17303        {
17304            self.write("dateTrunc(");
17305            self.generate_expression(&func.args[0])?;
17306            self.write(", ");
17307            self.generate_expression(&func.args[1])?;
17308            self.write(")");
17309            return Ok(());
17310        }
17311
17312        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17313        if matches!(
17314            self.config.dialect,
17315            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17316        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17317        {
17318            self.write_keyword("SUBSTR");
17319            self.write("(");
17320            for (i, arg) in func.args.iter().enumerate() {
17321                if i > 0 {
17322                    self.write(", ");
17323                }
17324                self.generate_expression(arg)?;
17325            }
17326            self.write(")");
17327            return Ok(());
17328        }
17329
17330        if self.config.dialect == Some(DialectType::Snowflake)
17331            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17332            && func.args.len() == 1
17333        {
17334            self.write_keyword("ARRAY_DISTINCT");
17335            self.write("(");
17336            self.write_keyword("ARRAY_COMPACT");
17337            self.write("(");
17338            self.generate_expression(&func.args[0])?;
17339            self.write("))");
17340            return Ok(());
17341        }
17342
17343        if self.config.dialect == Some(DialectType::Snowflake)
17344            && func.name.eq_ignore_ascii_case("LIST")
17345            && func.args.len() == 1
17346            && !matches!(func.args.first(), Some(Expression::Select(_)))
17347        {
17348            self.write_keyword("ARRAY_AGG");
17349            self.write("(");
17350            self.generate_expression(&func.args[0])?;
17351            self.write(")");
17352            return Ok(());
17353        }
17354
17355        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17356        if self.config.dialect == Some(DialectType::Redshift)
17357            && func.name.eq_ignore_ascii_case("CONCAT")
17358            && func.args.len() >= 2
17359        {
17360            for (i, arg) in func.args.iter().enumerate() {
17361                if i > 0 {
17362                    self.write(" || ");
17363                }
17364                self.generate_expression(arg)?;
17365            }
17366            return Ok(());
17367        }
17368
17369        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17370        if self.config.dialect == Some(DialectType::Redshift)
17371            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17372            && func.args.len() >= 2
17373        {
17374            let sep = &func.args[0];
17375            for (i, arg) in func.args.iter().skip(1).enumerate() {
17376                if i > 0 {
17377                    self.write(" || ");
17378                    self.generate_expression(sep)?;
17379                    self.write(" || ");
17380                }
17381                self.generate_expression(arg)?;
17382            }
17383            return Ok(());
17384        }
17385
17386        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17387        // Unit should be unquoted uppercase identifier
17388        if self.config.dialect == Some(DialectType::Redshift)
17389            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17390                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17391            && func.args.len() == 3
17392        {
17393            self.write_keyword("DATEDIFF");
17394            self.write("(");
17395            // First arg is unit - normalize to unquoted uppercase
17396            self.write_redshift_date_part(&func.args[0]);
17397            self.write(", ");
17398            self.generate_expression(&func.args[1])?;
17399            self.write(", ");
17400            self.generate_expression(&func.args[2])?;
17401            self.write(")");
17402            return Ok(());
17403        }
17404
17405        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17406        // Unit should be unquoted uppercase identifier
17407        if self.config.dialect == Some(DialectType::Redshift)
17408            && (func.name.eq_ignore_ascii_case("DATEADD")
17409                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17410            && func.args.len() == 3
17411        {
17412            self.write_keyword("DATEADD");
17413            self.write("(");
17414            // First arg is unit - normalize to unquoted uppercase
17415            self.write_redshift_date_part(&func.args[0]);
17416            self.write(", ");
17417            self.generate_expression(&func.args[1])?;
17418            self.write(", ");
17419            self.generate_expression(&func.args[2])?;
17420            self.write(")");
17421            return Ok(());
17422        }
17423
17424        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17425        if func.name.eq_ignore_ascii_case("UUID_STRING")
17426            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17427        {
17428            if matches!(
17429                self.config.dialect,
17430                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17431            ) {
17432                self.write_keyword("CAST");
17433                self.write("(");
17434                self.write_keyword("UUID");
17435                self.write("() ");
17436                self.write_keyword("AS");
17437                self.write(" ");
17438                self.write_keyword("STRING");
17439                self.write(")");
17440                return Ok(());
17441            }
17442
17443            if matches!(
17444                self.config.dialect,
17445                Some(DialectType::Presto | DialectType::Trino)
17446            ) {
17447                self.write_keyword("CAST");
17448                self.write("(");
17449                self.write_keyword("UUID");
17450                self.write("() ");
17451                self.write_keyword("AS");
17452                self.write(" ");
17453                self.write_keyword("VARCHAR");
17454                self.write(")");
17455                return Ok(());
17456            }
17457
17458            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17459                self.write("(SELECT LOWER(SUBSTRING(h, 1, 8) || '-' || SUBSTRING(h, 9, 4) || '-' || '5' || SUBSTRING(h, 14, 3) || '-' || FORMAT('{:02x}', CAST('0x' || SUBSTRING(h, 17, 2) AS INT) & 63 | 128) || SUBSTRING(h, 19, 2) || '-' || SUBSTRING(h, 21, 12)) FROM (SELECT SUBSTRING(SHA1(UNHEX(REPLACE(");
17460                self.generate_expression(&func.args[0])?;
17461                self.write(", '-', '')) || ENCODE(");
17462                self.generate_expression(&func.args[1])?;
17463                self.write(")), 1, 32) AS h))");
17464                return Ok(());
17465            }
17466
17467            let func_name = match self.config.dialect {
17468                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17469                Some(DialectType::BigQuery) => "GENERATE_UUID",
17470                _ => "UUID",
17471            };
17472            self.write_keyword(func_name);
17473            self.write("()");
17474            return Ok(());
17475        }
17476
17477        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17478        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17479        // Positional args are mapped to named parameters.
17480        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17481            && func.name.eq_ignore_ascii_case("GENERATOR")
17482        {
17483            let has_positional_args =
17484                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17485            if has_positional_args {
17486                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17487                self.write_keyword("GENERATOR");
17488                self.write("(");
17489                for (i, arg) in func.args.iter().enumerate() {
17490                    if i > 0 {
17491                        self.write(", ");
17492                    }
17493                    if i < param_names.len() {
17494                        self.write_keyword(param_names[i]);
17495                        self.write(" => ");
17496                        self.generate_expression(arg)?;
17497                    } else {
17498                        self.generate_expression(arg)?;
17499                    }
17500                }
17501                self.write(")");
17502                return Ok(());
17503            }
17504        }
17505
17506        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17507        // Unit should be quoted uppercase string
17508        if self.config.dialect == Some(DialectType::Redshift)
17509            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17510            && func.args.len() == 2
17511        {
17512            self.write_keyword("DATE_TRUNC");
17513            self.write("(");
17514            // First arg is unit - normalize to quoted uppercase
17515            self.write_redshift_date_part_quoted(&func.args[0]);
17516            self.write(", ");
17517            self.generate_expression(&func.args[1])?;
17518            self.write(")");
17519            return Ok(());
17520        }
17521
17522        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17523        if matches!(
17524            self.config.dialect,
17525            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17526        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17527            || func.name.eq_ignore_ascii_case("DATEPART"))
17528            && func.args.len() == 2
17529        {
17530            self.write_keyword("DATEPART");
17531            self.write("(");
17532            self.generate_expression(&func.args[0])?;
17533            self.write(", ");
17534            self.generate_expression(&func.args[1])?;
17535            self.write(")");
17536            return Ok(());
17537        }
17538
17539        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17540        if matches!(
17541            self.config.dialect,
17542            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17543        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17544            || func.name.eq_ignore_ascii_case("DATEPART"))
17545            && func.args.len() == 2
17546        {
17547            self.write_keyword("EXTRACT");
17548            self.write("(");
17549            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17550            match &func.args[0] {
17551                Expression::Literal(lit)
17552                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17553                {
17554                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17555                        unreachable!()
17556                    };
17557                    self.write(&s.to_ascii_lowercase());
17558                }
17559                _ => self.generate_expression(&func.args[0])?,
17560            }
17561            self.write_space();
17562            self.write_keyword("FROM");
17563            self.write_space();
17564            self.generate_expression(&func.args[1])?;
17565            self.write(")");
17566            return Ok(());
17567        }
17568
17569        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17570        if self.config.dialect == Some(DialectType::PostgreSQL)
17571            && matches!(
17572                func.name.to_ascii_uppercase().as_str(),
17573                "DATE_ADD" | "DATE_SUB"
17574            )
17575            && func.args.len() == 2
17576            && matches!(func.args[1], Expression::Interval(_))
17577        {
17578            self.generate_expression(&func.args[0])?;
17579            self.write_space();
17580            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17581                self.write("-");
17582            } else {
17583                self.write("+");
17584            }
17585            self.write_space();
17586            self.generate_expression(&func.args[1])?;
17587            return Ok(());
17588        }
17589
17590        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17591        // Also DATE literals in Dremio should be CAST(...AS DATE)
17592        if self.config.dialect == Some(DialectType::Dremio)
17593            && (func.name.eq_ignore_ascii_case("DATE_PART")
17594                || func.name.eq_ignore_ascii_case("DATEPART"))
17595            && func.args.len() == 2
17596        {
17597            self.write_keyword("EXTRACT");
17598            self.write("(");
17599            self.generate_expression(&func.args[0])?;
17600            self.write_space();
17601            self.write_keyword("FROM");
17602            self.write_space();
17603            // For Dremio, DATE literals should become CAST('value' AS DATE)
17604            self.generate_dremio_date_expression(&func.args[1])?;
17605            self.write(")");
17606            return Ok(());
17607        }
17608
17609        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17610        if self.config.dialect == Some(DialectType::Dremio)
17611            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17612            && func.args.is_empty()
17613        {
17614            self.write_keyword("CURRENT_DATE_UTC");
17615            return Ok(());
17616        }
17617
17618        // Dremio: DATETYPE(year, month, day) transformation
17619        // - If all args are integer literals: DATE('YYYY-MM-DD')
17620        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17621        if self.config.dialect == Some(DialectType::Dremio)
17622            && func.name.eq_ignore_ascii_case("DATETYPE")
17623            && func.args.len() == 3
17624        {
17625            // Helper function to extract integer from number literal
17626            fn get_int_literal(expr: &Expression) -> Option<i64> {
17627                if let Expression::Literal(lit) = expr {
17628                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17629                        s.parse::<i64>().ok()
17630                    } else {
17631                        None
17632                    }
17633                } else {
17634                    None
17635                }
17636            }
17637
17638            // Check if all arguments are integer literals
17639            if let (Some(year), Some(month), Some(day)) = (
17640                get_int_literal(&func.args[0]),
17641                get_int_literal(&func.args[1]),
17642                get_int_literal(&func.args[2]),
17643            ) {
17644                // All are integer literals: DATE('YYYY-MM-DD')
17645                self.write_keyword("DATE");
17646                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17647                return Ok(());
17648            }
17649
17650            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17651            self.write_keyword("CAST");
17652            self.write("(");
17653            self.write_keyword("CONCAT");
17654            self.write("(");
17655            self.generate_expression(&func.args[0])?;
17656            self.write(", '-', ");
17657            self.generate_expression(&func.args[1])?;
17658            self.write(", '-', ");
17659            self.generate_expression(&func.args[2])?;
17660            self.write(")");
17661            self.write_space();
17662            self.write_keyword("AS");
17663            self.write_space();
17664            self.write_keyword("DATE");
17665            self.write(")");
17666            return Ok(());
17667        }
17668
17669        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17670        // when it's not an integer literal
17671        let is_presto_like = matches!(
17672            self.config.dialect,
17673            Some(DialectType::Presto) | Some(DialectType::Trino)
17674        );
17675        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17676            self.write_keyword("DATE_ADD");
17677            self.write("(");
17678            // First arg: unit (pass through as-is, e.g., 'DAY')
17679            self.generate_expression(&func.args[0])?;
17680            self.write(", ");
17681            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17682            let interval = &func.args[1];
17683            let needs_cast = !self.returns_integer_type(interval);
17684            if needs_cast {
17685                self.write_keyword("CAST");
17686                self.write("(");
17687            }
17688            self.generate_expression(interval)?;
17689            if needs_cast {
17690                self.write_space();
17691                self.write_keyword("AS");
17692                self.write_space();
17693                self.write_keyword("BIGINT");
17694                self.write(")");
17695            }
17696            self.write(", ");
17697            // Third arg: date
17698            self.generate_expression(&func.args[2])?;
17699            self.write(")");
17700            return Ok(());
17701        }
17702
17703        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17704        let use_brackets = func.use_bracket_syntax;
17705
17706        // Special case: functions WITH ORDINALITY need special output order
17707        // Input: FUNC(args) WITH ORDINALITY
17708        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17709        // Output must be: FUNC(args) WITH ORDINALITY
17710        let has_ordinality = func.name.len() >= 16
17711            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17712        let output_name = if has_ordinality {
17713            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17714            self.normalize_func_name(base_name)
17715        } else {
17716            normalized_name.clone()
17717        };
17718
17719        // For qualified names (schema.function or object.method), preserve original case
17720        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17721        let quote_source_clickhouse_function =
17722            matches!(self.config.dialect, Some(DialectType::ClickHouse))
17723                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
17724                && func.quoted;
17725
17726        if quote_source_clickhouse_function {
17727            self.generate_identifier(&Identifier {
17728                name: func.name.clone(),
17729                quoted: true,
17730                trailing_comments: Vec::new(),
17731                span: None,
17732            })?;
17733        } else if func.name.contains('.') && !has_ordinality {
17734            // Don't normalize qualified functions - preserve original case
17735            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17736            if func.quoted {
17737                self.write("`");
17738                self.write(&func.name);
17739                self.write("`");
17740            } else {
17741                self.write(&func.name);
17742            }
17743        } else {
17744            self.write(&output_name);
17745        }
17746
17747        // If no_parens is true and there are no args, output just the function name
17748        // Unless the target dialect requires parens for this function
17749        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17750            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17751                || func.name.eq_ignore_ascii_case("SESSION_USER")
17752                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17753            {
17754                matches!(
17755                    self.config.dialect,
17756                    Some(DialectType::Snowflake)
17757                        | Some(DialectType::Spark)
17758                        | Some(DialectType::Databricks)
17759                        | Some(DialectType::Hive)
17760                )
17761            } else {
17762                false
17763            };
17764            !needs_parens
17765        };
17766        if force_parens {
17767            // Output trailing comments
17768            for comment in &func.trailing_comments {
17769                self.write_space();
17770                self.write_formatted_comment(comment);
17771            }
17772            return Ok(());
17773        }
17774
17775        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17776        if func.name.eq_ignore_ascii_case("CUBE")
17777            || func.name.eq_ignore_ascii_case("ROLLUP")
17778            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17779        {
17780            self.write(" (");
17781        } else if use_brackets {
17782            self.write("[");
17783        } else {
17784            self.write("(");
17785        }
17786        if func.distinct {
17787            self.write_keyword("DISTINCT");
17788            self.write_space();
17789        }
17790
17791        // Check if arguments should be split onto multiple lines (pretty + too wide)
17792        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17793            && (func.name.eq_ignore_ascii_case("TABLE")
17794                || func.name.eq_ignore_ascii_case("FLATTEN"));
17795        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17796        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17797            || func.name.eq_ignore_ascii_case("CUBE")
17798            || func.name.eq_ignore_ascii_case("ROLLUP");
17799        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17800            if is_grouping_func {
17801                true
17802            } else {
17803                // Pre-render arguments to check total width
17804                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17805                for arg in &func.args {
17806                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17807                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17808                    temp_gen.generate_expression(arg)?;
17809                    expr_strings.push(temp_gen.output);
17810                }
17811                self.too_wide(&expr_strings)
17812            }
17813        } else {
17814            false
17815        };
17816
17817        if should_split {
17818            // Split onto multiple lines
17819            self.write_newline();
17820            self.indent_level += 1;
17821            for (i, arg) in func.args.iter().enumerate() {
17822                self.write_indent();
17823                self.generate_expression(arg)?;
17824                if i + 1 < func.args.len() {
17825                    self.write(",");
17826                }
17827                self.write_newline();
17828            }
17829            self.indent_level -= 1;
17830            self.write_indent();
17831        } else {
17832            // All on one line
17833            for (i, arg) in func.args.iter().enumerate() {
17834                if i > 0 {
17835                    self.write(", ");
17836                }
17837                self.generate_expression(arg)?;
17838            }
17839        }
17840
17841        if use_brackets {
17842            self.write("]");
17843        } else {
17844            self.write(")");
17845        }
17846        // Append WITH ORDINALITY after closing paren for table-valued functions
17847        if has_ordinality {
17848            self.write_space();
17849            self.write_keyword("WITH ORDINALITY");
17850        }
17851        // Output trailing comments
17852        for comment in &func.trailing_comments {
17853            self.write_space();
17854            self.write_formatted_comment(comment);
17855        }
17856        Ok(())
17857    }
17858
17859    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17860        self.generate_expression(&fe.this)?;
17861        self.write_keyword(" EMITS ");
17862        self.generate_expression(&fe.emits)?;
17863        Ok(())
17864    }
17865
17866    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17867        // Normalize function name based on dialect settings
17868        let mut normalized_name = self.normalize_func_name(&func.name);
17869
17870        // Dialect-specific name mappings for aggregate functions
17871        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17872            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17873            match self.config.dialect {
17874                Some(DialectType::ClickHouse) => {
17875                    normalized_name = if is_max {
17876                        Cow::Borrowed("argMax")
17877                    } else {
17878                        Cow::Borrowed("argMin")
17879                    };
17880                }
17881                Some(DialectType::DuckDB) => {
17882                    normalized_name = if is_max {
17883                        Cow::Borrowed("ARG_MAX")
17884                    } else {
17885                        Cow::Borrowed("ARG_MIN")
17886                    };
17887                }
17888                _ => {}
17889            }
17890        }
17891        self.write(normalized_name.as_ref());
17892        self.write("(");
17893        if func.distinct {
17894            self.write_keyword("DISTINCT");
17895            self.write_space();
17896        }
17897
17898        // Check if we need to transform multi-arg COUNT DISTINCT
17899        // When dialect doesn't support multi_arg_distinct, transform:
17900        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17901        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17902        let needs_multi_arg_transform =
17903            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17904
17905        if needs_multi_arg_transform {
17906            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17907            self.write_keyword("CASE");
17908            for arg in &func.args {
17909                self.write_space();
17910                self.write_keyword("WHEN");
17911                self.write_space();
17912                self.generate_expression(arg)?;
17913                self.write_space();
17914                self.write_keyword("IS NULL THEN NULL");
17915            }
17916            self.write_space();
17917            self.write_keyword("ELSE");
17918            self.write(" (");
17919            for (i, arg) in func.args.iter().enumerate() {
17920                if i > 0 {
17921                    self.write(", ");
17922                }
17923                self.generate_expression(arg)?;
17924            }
17925            self.write(")");
17926            self.write_space();
17927            self.write_keyword("END");
17928        } else {
17929            for (i, arg) in func.args.iter().enumerate() {
17930                if i > 0 {
17931                    self.write(", ");
17932                }
17933                self.generate_expression(arg)?;
17934            }
17935        }
17936
17937        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17938        let clickhouse_ignore_nulls_outside =
17939            matches!(self.config.dialect, Some(DialectType::ClickHouse));
17940        if self.config.ignore_nulls_in_func
17941            && !matches!(
17942                self.config.dialect,
17943                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
17944            )
17945        {
17946            if let Some(ignore) = func.ignore_nulls {
17947                self.write_space();
17948                if ignore {
17949                    self.write_keyword("IGNORE NULLS");
17950                } else {
17951                    self.write_keyword("RESPECT NULLS");
17952                }
17953            }
17954        }
17955
17956        // ORDER BY inside aggregate
17957        if !func.order_by.is_empty() {
17958            self.write_space();
17959            self.write_keyword("ORDER BY");
17960            self.write_space();
17961            for (i, ord) in func.order_by.iter().enumerate() {
17962                if i > 0 {
17963                    self.write(", ");
17964                }
17965                self.generate_ordered(ord)?;
17966            }
17967        }
17968
17969        // LIMIT inside aggregate
17970        if let Some(limit) = &func.limit {
17971            self.write_space();
17972            self.write_keyword("LIMIT");
17973            self.write_space();
17974            // Check if this is a Tuple representing LIMIT offset, count
17975            if let Expression::Tuple(t) = limit.as_ref() {
17976                if t.expressions.len() == 2 {
17977                    self.generate_expression(&t.expressions[0])?;
17978                    self.write(", ");
17979                    self.generate_expression(&t.expressions[1])?;
17980                } else {
17981                    self.generate_expression(limit)?;
17982                }
17983            } else {
17984                self.generate_expression(limit)?;
17985            }
17986        }
17987
17988        self.write(")");
17989
17990        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17991        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
17992            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17993        {
17994            if let Some(ignore) = func.ignore_nulls {
17995                self.write_space();
17996                if ignore {
17997                    self.write_keyword("IGNORE NULLS");
17998                } else {
17999                    self.write_keyword("RESPECT NULLS");
18000                }
18001            }
18002        }
18003
18004        if let Some(filter) = &func.filter {
18005            self.write_space();
18006            self.write_keyword("FILTER");
18007            self.write("(");
18008            self.write_keyword("WHERE");
18009            self.write_space();
18010            self.generate_expression(filter)?;
18011            self.write(")");
18012        }
18013
18014        Ok(())
18015    }
18016
18017    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18018        self.generate_expression(&wf.this)?;
18019
18020        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18021        if let Some(keep) = &wf.keep {
18022            self.write_space();
18023            self.write_keyword("KEEP");
18024            self.write(" (");
18025            self.write_keyword("DENSE_RANK");
18026            self.write_space();
18027            if keep.first {
18028                self.write_keyword("FIRST");
18029            } else {
18030                self.write_keyword("LAST");
18031            }
18032            self.write_space();
18033            self.write_keyword("ORDER BY");
18034            self.write_space();
18035            for (i, ord) in keep.order_by.iter().enumerate() {
18036                if i > 0 {
18037                    self.write(", ");
18038                }
18039                self.generate_ordered(ord)?;
18040            }
18041            self.write(")");
18042        }
18043
18044        // Check if there's any OVER clause content
18045        let has_over = !wf.over.partition_by.is_empty()
18046            || !wf.over.order_by.is_empty()
18047            || wf.over.frame.is_some()
18048            || wf.over.window_name.is_some();
18049
18050        // Only output OVER if there's actual window specification (not just KEEP alone)
18051        if has_over {
18052            self.write_space();
18053            self.write_keyword("OVER");
18054
18055            // Check if this is just a bare named window reference (no parens needed)
18056            let has_specs = !wf.over.partition_by.is_empty()
18057                || !wf.over.order_by.is_empty()
18058                || wf.over.frame.is_some();
18059
18060            if wf.over.window_name.is_some() && !has_specs {
18061                // OVER window_name (without parentheses)
18062                self.write_space();
18063                self.write(&wf.over.window_name.as_ref().unwrap().name);
18064            } else {
18065                // OVER (...) or OVER (window_name ...)
18066                self.write(" (");
18067                self.generate_over(&wf.over)?;
18068                self.write(")");
18069            }
18070        } else if wf.keep.is_none() {
18071            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18072            self.write_space();
18073            self.write_keyword("OVER");
18074            self.write(" ()");
18075        }
18076
18077        Ok(())
18078    }
18079
18080    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18081    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18082        self.generate_expression(&wg.this)?;
18083        self.write_space();
18084        self.write_keyword("WITHIN GROUP");
18085        self.write(" (");
18086        self.write_keyword("ORDER BY");
18087        self.write_space();
18088        for (i, ord) in wg.order_by.iter().enumerate() {
18089            if i > 0 {
18090                self.write(", ");
18091            }
18092            self.generate_ordered(ord)?;
18093        }
18094        self.write(")");
18095        Ok(())
18096    }
18097
18098    /// Generate the contents of an OVER clause (without parentheses)
18099    fn generate_over(&mut self, over: &Over) -> Result<()> {
18100        let mut has_content = false;
18101
18102        // Named window reference
18103        if let Some(name) = &over.window_name {
18104            self.write(&name.name);
18105            has_content = true;
18106        }
18107
18108        // PARTITION BY
18109        if !over.partition_by.is_empty() {
18110            if has_content {
18111                self.write_space();
18112            }
18113            self.write_keyword("PARTITION BY");
18114            self.write_space();
18115            for (i, expr) in over.partition_by.iter().enumerate() {
18116                if i > 0 {
18117                    self.write(", ");
18118                }
18119                self.generate_expression(expr)?;
18120            }
18121            has_content = true;
18122        }
18123
18124        // ORDER BY
18125        if !over.order_by.is_empty() {
18126            if has_content {
18127                self.write_space();
18128            }
18129            self.write_keyword("ORDER BY");
18130            self.write_space();
18131            for (i, ordered) in over.order_by.iter().enumerate() {
18132                if i > 0 {
18133                    self.write(", ");
18134                }
18135                self.generate_ordered(ordered)?;
18136            }
18137            has_content = true;
18138        }
18139
18140        // Window frame
18141        if let Some(frame) = &over.frame {
18142            if has_content {
18143                self.write_space();
18144            }
18145            self.generate_window_frame(frame)?;
18146        }
18147
18148        Ok(())
18149    }
18150
18151    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18152        // Exasol uses lowercase for frame kind (rows/range/groups)
18153        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18154
18155        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18156        if !lowercase_frame {
18157            if let Some(kind_text) = &frame.kind_text {
18158                self.write(kind_text);
18159            } else {
18160                match frame.kind {
18161                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18162                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18163                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18164                }
18165            }
18166        } else {
18167            match frame.kind {
18168                WindowFrameKind::Rows => self.write("rows"),
18169                WindowFrameKind::Range => self.write("range"),
18170                WindowFrameKind::Groups => self.write("groups"),
18171            }
18172        }
18173
18174        // Use BETWEEN format only when there's an explicit end bound,
18175        // or when normalize_window_frame_between is enabled and the start is a directional bound
18176        self.write_space();
18177        let should_normalize = self.config.normalize_window_frame_between
18178            && frame.end.is_none()
18179            && matches!(
18180                frame.start,
18181                WindowFrameBound::Preceding(_)
18182                    | WindowFrameBound::Following(_)
18183                    | WindowFrameBound::UnboundedPreceding
18184                    | WindowFrameBound::UnboundedFollowing
18185            );
18186
18187        if let Some(end) = &frame.end {
18188            // BETWEEN format: RANGE BETWEEN start AND end
18189            self.write_keyword("BETWEEN");
18190            self.write_space();
18191            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18192            self.write_space();
18193            self.write_keyword("AND");
18194            self.write_space();
18195            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18196        } else if should_normalize {
18197            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18198            self.write_keyword("BETWEEN");
18199            self.write_space();
18200            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18201            self.write_space();
18202            self.write_keyword("AND");
18203            self.write_space();
18204            self.write_keyword("CURRENT ROW");
18205        } else {
18206            // Single bound format: RANGE CURRENT ROW
18207            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18208        }
18209
18210        // EXCLUDE clause
18211        if let Some(exclude) = &frame.exclude {
18212            self.write_space();
18213            self.write_keyword("EXCLUDE");
18214            self.write_space();
18215            match exclude {
18216                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18217                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18218                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18219                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18220            }
18221        }
18222
18223        Ok(())
18224    }
18225
18226    fn generate_window_frame_bound(
18227        &mut self,
18228        bound: &WindowFrameBound,
18229        side_text: Option<&str>,
18230    ) -> Result<()> {
18231        // Exasol uses lowercase for preceding/following
18232        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18233
18234        match bound {
18235            WindowFrameBound::CurrentRow => {
18236                self.write_keyword("CURRENT ROW");
18237            }
18238            WindowFrameBound::UnboundedPreceding => {
18239                self.write_keyword("UNBOUNDED");
18240                self.write_space();
18241                if lowercase_frame {
18242                    self.write("preceding");
18243                } else if let Some(text) = side_text {
18244                    self.write(text);
18245                } else {
18246                    self.write_keyword("PRECEDING");
18247                }
18248            }
18249            WindowFrameBound::UnboundedFollowing => {
18250                self.write_keyword("UNBOUNDED");
18251                self.write_space();
18252                if lowercase_frame {
18253                    self.write("following");
18254                } else if let Some(text) = side_text {
18255                    self.write(text);
18256                } else {
18257                    self.write_keyword("FOLLOWING");
18258                }
18259            }
18260            WindowFrameBound::Preceding(expr) => {
18261                self.generate_expression(expr)?;
18262                self.write_space();
18263                if lowercase_frame {
18264                    self.write("preceding");
18265                } else if let Some(text) = side_text {
18266                    self.write(text);
18267                } else {
18268                    self.write_keyword("PRECEDING");
18269                }
18270            }
18271            WindowFrameBound::Following(expr) => {
18272                self.generate_expression(expr)?;
18273                self.write_space();
18274                if lowercase_frame {
18275                    self.write("following");
18276                } else if let Some(text) = side_text {
18277                    self.write(text);
18278                } else {
18279                    self.write_keyword("FOLLOWING");
18280                }
18281            }
18282            WindowFrameBound::BarePreceding => {
18283                if lowercase_frame {
18284                    self.write("preceding");
18285                } else if let Some(text) = side_text {
18286                    self.write(text);
18287                } else {
18288                    self.write_keyword("PRECEDING");
18289                }
18290            }
18291            WindowFrameBound::BareFollowing => {
18292                if lowercase_frame {
18293                    self.write("following");
18294                } else if let Some(text) = side_text {
18295                    self.write(text);
18296                } else {
18297                    self.write_keyword("FOLLOWING");
18298                }
18299            }
18300            WindowFrameBound::Value(expr) => {
18301                // Bare numeric bound without PRECEDING/FOLLOWING
18302                self.generate_expression(expr)?;
18303            }
18304        }
18305        Ok(())
18306    }
18307
18308    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18309        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18310        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18311        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18312            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18313            && !matches!(&interval.this, Some(Expression::Literal(_)));
18314
18315        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18316        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18317        if self.config.single_string_interval {
18318            if let (
18319                Some(Expression::Literal(lit)),
18320                Some(IntervalUnitSpec::Simple {
18321                    ref unit,
18322                    ref use_plural,
18323                }),
18324            ) = (&interval.this, &interval.unit)
18325            {
18326                if let Literal::String(ref val) = lit.as_ref() {
18327                    self.write_keyword("INTERVAL");
18328                    self.write_space();
18329                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18330                    let unit_str = self.interval_unit_str(unit, effective_plural);
18331                    self.write("'");
18332                    self.write(val);
18333                    self.write(" ");
18334                    self.write(&unit_str);
18335                    self.write("'");
18336                    return Ok(());
18337                }
18338            }
18339        }
18340
18341        if !skip_interval_keyword {
18342            self.write_keyword("INTERVAL");
18343        }
18344
18345        // Generate value if present
18346        if let Some(ref value) = interval.this {
18347            if !skip_interval_keyword {
18348                self.write_space();
18349            }
18350            // If the value is a complex expression (not a literal/column/function call)
18351            // and there's a unit, wrap it in parentheses
18352            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18353            let needs_parens = interval.unit.is_some()
18354                && matches!(
18355                    value,
18356                    Expression::Add(_)
18357                        | Expression::Sub(_)
18358                        | Expression::Mul(_)
18359                        | Expression::Div(_)
18360                        | Expression::Mod(_)
18361                        | Expression::BitwiseAnd(_)
18362                        | Expression::BitwiseOr(_)
18363                        | Expression::BitwiseXor(_)
18364                );
18365            if needs_parens {
18366                self.write("(");
18367            }
18368            self.generate_expression(value)?;
18369            if needs_parens {
18370                self.write(")");
18371            }
18372        }
18373
18374        // Generate unit if present
18375        if let Some(ref unit_spec) = interval.unit {
18376            self.write_space();
18377            self.write_interval_unit_spec(unit_spec)?;
18378        }
18379
18380        Ok(())
18381    }
18382
18383    /// Return the string representation of an interval unit
18384    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18385        match (unit, use_plural) {
18386            (IntervalUnit::Year, false) => "YEAR",
18387            (IntervalUnit::Year, true) => "YEARS",
18388            (IntervalUnit::Quarter, false) => "QUARTER",
18389            (IntervalUnit::Quarter, true) => "QUARTERS",
18390            (IntervalUnit::Month, false) => "MONTH",
18391            (IntervalUnit::Month, true) => "MONTHS",
18392            (IntervalUnit::Week, false) => "WEEK",
18393            (IntervalUnit::Week, true) => "WEEKS",
18394            (IntervalUnit::Day, false) => "DAY",
18395            (IntervalUnit::Day, true) => "DAYS",
18396            (IntervalUnit::Hour, false) => "HOUR",
18397            (IntervalUnit::Hour, true) => "HOURS",
18398            (IntervalUnit::Minute, false) => "MINUTE",
18399            (IntervalUnit::Minute, true) => "MINUTES",
18400            (IntervalUnit::Second, false) => "SECOND",
18401            (IntervalUnit::Second, true) => "SECONDS",
18402            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18403            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18404            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18405            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18406            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18407            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18408        }
18409    }
18410
18411    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18412        match unit_spec {
18413            IntervalUnitSpec::Simple { unit, use_plural } => {
18414                // If dialect doesn't allow plural forms, force singular
18415                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18416                self.write_simple_interval_unit(unit, effective_plural);
18417            }
18418            IntervalUnitSpec::Span(span) => {
18419                self.write_simple_interval_unit(&span.this, false);
18420                self.write_space();
18421                self.write_keyword("TO");
18422                self.write_space();
18423                self.write_simple_interval_unit(&span.expression, false);
18424            }
18425            IntervalUnitSpec::ExprSpan(span) => {
18426                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18427                self.generate_expression(&span.this)?;
18428                self.write_space();
18429                self.write_keyword("TO");
18430                self.write_space();
18431                self.generate_expression(&span.expression)?;
18432            }
18433            IntervalUnitSpec::Expr(expr) => {
18434                self.generate_expression(expr)?;
18435            }
18436        }
18437        Ok(())
18438    }
18439
18440    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18441        // Output interval unit, respecting plural preference
18442        match (unit, use_plural) {
18443            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18444            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18445            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18446            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18447            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18448            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18449            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18450            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18451            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18452            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18453            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18454            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18455            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18456            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18457            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18458            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18459            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18460            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18461            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18462            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18463            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18464            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18465        }
18466    }
18467
18468    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18469    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18470    fn write_redshift_date_part(&mut self, expr: &Expression) {
18471        let part_str = self.extract_date_part_string(expr);
18472        if let Some(part) = part_str {
18473            let normalized = self.normalize_date_part(&part);
18474            self.write_keyword(&normalized);
18475        } else {
18476            // If we can't extract a date part string, fall back to generating the expression
18477            let _ = self.generate_expression(expr);
18478        }
18479    }
18480
18481    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18482    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18483    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18484        let part_str = self.extract_date_part_string(expr);
18485        if let Some(part) = part_str {
18486            let normalized = self.normalize_date_part(&part);
18487            self.write("'");
18488            self.write(&normalized);
18489            self.write("'");
18490        } else {
18491            // If we can't extract a date part string, fall back to generating the expression
18492            let _ = self.generate_expression(expr);
18493        }
18494    }
18495
18496    /// Extract date part string from expression (handles string literals and identifiers)
18497    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18498        match expr {
18499            Expression::Literal(lit)
18500                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18501            {
18502                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18503                    unreachable!()
18504                };
18505                Some(s.clone())
18506            }
18507            Expression::Identifier(id) => Some(id.name.clone()),
18508            Expression::Var(v) => Some(v.this.clone()),
18509            Expression::Column(col) if col.table.is_none() => {
18510                // Simple column reference without table prefix, treat as identifier
18511                Some(col.name.name.clone())
18512            }
18513            _ => None,
18514        }
18515    }
18516
18517    /// Normalize date part to uppercase singular form
18518    /// days -> DAY, months -> MONTH, etc.
18519    fn normalize_date_part(&self, part: &str) -> String {
18520        let mut buf = [0u8; 64];
18521        let lower: &str = if part.len() <= 64 {
18522            for (i, b) in part.bytes().enumerate() {
18523                buf[i] = b.to_ascii_lowercase();
18524            }
18525            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18526        } else {
18527            return part.to_ascii_uppercase();
18528        };
18529        match lower {
18530            "day" | "days" | "d" => "DAY".to_string(),
18531            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
18532            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18533            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18534            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18535            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18536            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18537            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18538            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18539            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18540            _ => part.to_ascii_uppercase(),
18541        }
18542    }
18543
18544    fn write_datetime_field(&mut self, field: &DateTimeField) {
18545        match field {
18546            DateTimeField::Year => self.write_keyword("YEAR"),
18547            DateTimeField::Month => self.write_keyword("MONTH"),
18548            DateTimeField::Day => self.write_keyword("DAY"),
18549            DateTimeField::Hour => self.write_keyword("HOUR"),
18550            DateTimeField::Minute => self.write_keyword("MINUTE"),
18551            DateTimeField::Second => self.write_keyword("SECOND"),
18552            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18553            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18554            DateTimeField::DayOfWeek => {
18555                let name = match self.config.dialect {
18556                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18557                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18558                    _ => "DOW",
18559                };
18560                self.write_keyword(name);
18561            }
18562            DateTimeField::DayOfYear => {
18563                let name = match self.config.dialect {
18564                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18565                    _ => "DOY",
18566                };
18567                self.write_keyword(name);
18568            }
18569            DateTimeField::Week => self.write_keyword("WEEK"),
18570            DateTimeField::WeekWithModifier(modifier) => {
18571                self.write_keyword("WEEK");
18572                self.write("(");
18573                self.write(modifier);
18574                self.write(")");
18575            }
18576            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18577            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18578            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18579            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18580            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18581            DateTimeField::Date => self.write_keyword("DATE"),
18582            DateTimeField::Time => self.write_keyword("TIME"),
18583            DateTimeField::Custom(name) => self.write(name),
18584        }
18585    }
18586
18587    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18588    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18589        match field {
18590            DateTimeField::Year => self.write("year"),
18591            DateTimeField::Month => self.write("month"),
18592            DateTimeField::Day => self.write("day"),
18593            DateTimeField::Hour => self.write("hour"),
18594            DateTimeField::Minute => self.write("minute"),
18595            DateTimeField::Second => self.write("second"),
18596            DateTimeField::Millisecond => self.write("millisecond"),
18597            DateTimeField::Microsecond => self.write("microsecond"),
18598            DateTimeField::DayOfWeek => self.write("dow"),
18599            DateTimeField::DayOfYear => self.write("doy"),
18600            DateTimeField::Week => self.write("week"),
18601            DateTimeField::WeekWithModifier(modifier) => {
18602                self.write("week(");
18603                self.write(modifier);
18604                self.write(")");
18605            }
18606            DateTimeField::Quarter => self.write("quarter"),
18607            DateTimeField::Epoch => self.write("epoch"),
18608            DateTimeField::Timezone => self.write("timezone"),
18609            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18610            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18611            DateTimeField::Date => self.write("date"),
18612            DateTimeField::Time => self.write("time"),
18613            DateTimeField::Custom(name) => self.write(name),
18614        }
18615    }
18616
18617    // Helper function generators
18618
18619    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18620        self.write_keyword(name);
18621        self.write("(");
18622        self.generate_expression(arg)?;
18623        self.write(")");
18624        Ok(())
18625    }
18626
18627    /// Generate a unary function, using the original name if available for round-trip preservation
18628    fn generate_unary_func(
18629        &mut self,
18630        default_name: &str,
18631        f: &crate::expressions::UnaryFunc,
18632    ) -> Result<()> {
18633        let name = f.original_name.as_deref().unwrap_or(default_name);
18634        self.write_keyword(name);
18635        self.write("(");
18636        self.generate_expression(&f.this)?;
18637        self.write(")");
18638        Ok(())
18639    }
18640
18641    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18642    fn generate_sqrt_cbrt(
18643        &mut self,
18644        f: &crate::expressions::UnaryFunc,
18645        func_name: &str,
18646        _op: &str,
18647    ) -> Result<()> {
18648        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18649        // Always use function syntax for consistency
18650        self.write_keyword(func_name);
18651        self.write("(");
18652        self.generate_expression(&f.this)?;
18653        self.write(")");
18654        Ok(())
18655    }
18656
18657    fn generate_binary_func(
18658        &mut self,
18659        name: &str,
18660        arg1: &Expression,
18661        arg2: &Expression,
18662    ) -> Result<()> {
18663        self.write_keyword(name);
18664        self.write("(");
18665        self.generate_expression(arg1)?;
18666        self.write(", ");
18667        self.generate_expression(arg2)?;
18668        self.write(")");
18669        Ok(())
18670    }
18671
18672    /// Generate CHAR/CHR function with optional USING charset
18673    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18674    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18675    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18676        // Use stored name if available, otherwise default to CHAR
18677        let func_name = f.name.as_deref().unwrap_or("CHAR");
18678        self.write_keyword(func_name);
18679        self.write("(");
18680        for (i, arg) in f.args.iter().enumerate() {
18681            if i > 0 {
18682                self.write(", ");
18683            }
18684            self.generate_expression(arg)?;
18685        }
18686        if let Some(ref charset) = f.charset {
18687            self.write(" ");
18688            self.write_keyword("USING");
18689            self.write(" ");
18690            self.write(charset);
18691        }
18692        self.write(")");
18693        Ok(())
18694    }
18695
18696    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18697        use crate::dialects::DialectType;
18698
18699        match self.config.dialect {
18700            Some(DialectType::Teradata) => {
18701                // Teradata uses ** operator for exponentiation
18702                self.generate_expression(&f.this)?;
18703                self.write(" ** ");
18704                self.generate_expression(&f.expression)?;
18705                Ok(())
18706            }
18707            _ => {
18708                // Other dialects use POWER function
18709                self.generate_binary_func("POWER", &f.this, &f.expression)
18710            }
18711        }
18712    }
18713
18714    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18715        self.write_func_name(name);
18716        self.write("(");
18717        for (i, arg) in args.iter().enumerate() {
18718            if i > 0 {
18719                self.write(", ");
18720            }
18721            self.generate_expression(arg)?;
18722        }
18723        self.write(")");
18724        Ok(())
18725    }
18726
18727    // String function generators
18728
18729    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18730        self.write_keyword("CONCAT_WS");
18731        self.write("(");
18732        self.generate_expression(&f.separator)?;
18733        for expr in &f.expressions {
18734            self.write(", ");
18735            self.generate_expression(expr)?;
18736        }
18737        self.write(")");
18738        Ok(())
18739    }
18740
18741    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18742        if let Expression::Concat(op) = expr {
18743            Self::collect_concat_operands(&op.left, out);
18744            Self::collect_concat_operands(&op.right, out);
18745        } else {
18746            out.push(expr);
18747        }
18748    }
18749
18750    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18751        let mut operands = Vec::new();
18752        Self::collect_concat_operands(&op.left, &mut operands);
18753        Self::collect_concat_operands(&op.right, &mut operands);
18754
18755        self.write_keyword("CONCAT");
18756        self.write("(");
18757        for (i, operand) in operands.iter().enumerate() {
18758            if i > 0 {
18759                self.write(", ");
18760            }
18761            self.generate_expression(operand)?;
18762        }
18763        self.write(")");
18764        Ok(())
18765    }
18766
18767    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18768        if let Expression::DPipe(dpipe) = expr {
18769            Self::collect_dpipe_operands(&dpipe.this, out);
18770            Self::collect_dpipe_operands(&dpipe.expression, out);
18771        } else {
18772            out.push(expr);
18773        }
18774    }
18775
18776    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18777        let mut operands = Vec::new();
18778        Self::collect_dpipe_operands(&e.this, &mut operands);
18779        Self::collect_dpipe_operands(&e.expression, &mut operands);
18780
18781        self.write_keyword("CONCAT");
18782        self.write("(");
18783        for (i, operand) in operands.iter().enumerate() {
18784            if i > 0 {
18785                self.write(", ");
18786            }
18787            self.generate_expression(operand)?;
18788        }
18789        self.write(")");
18790        Ok(())
18791    }
18792
18793    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18794        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
18795        let use_substr = matches!(
18796            self.config.dialect,
18797            Some(
18798                DialectType::Oracle
18799                    | DialectType::Presto
18800                    | DialectType::Trino
18801                    | DialectType::Athena
18802            )
18803        );
18804        if use_substr {
18805            self.write_keyword("SUBSTR");
18806        } else {
18807            self.write_keyword("SUBSTRING");
18808        }
18809        self.write("(");
18810        self.generate_expression(&f.this)?;
18811        // PostgreSQL always uses FROM/FOR syntax
18812        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18813        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
18814        let use_comma_syntax = matches!(
18815            self.config.dialect,
18816            Some(DialectType::Spark)
18817                | Some(DialectType::Hive)
18818                | Some(DialectType::Databricks)
18819                | Some(DialectType::TSQL)
18820                | Some(DialectType::Fabric)
18821        );
18822        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18823            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18824            self.write_space();
18825            self.write_keyword("FROM");
18826            self.write_space();
18827            self.generate_expression(&f.start)?;
18828            if let Some(length) = &f.length {
18829                self.write_space();
18830                self.write_keyword("FOR");
18831                self.write_space();
18832                self.generate_expression(length)?;
18833            }
18834        } else {
18835            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18836            self.write(", ");
18837            self.generate_expression(&f.start)?;
18838            if let Some(length) = &f.length {
18839                self.write(", ");
18840                self.generate_expression(length)?;
18841            }
18842        }
18843        self.write(")");
18844        Ok(())
18845    }
18846
18847    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18848        self.write_keyword("OVERLAY");
18849        self.write("(");
18850        self.generate_expression(&f.this)?;
18851        self.write_space();
18852        self.write_keyword("PLACING");
18853        self.write_space();
18854        self.generate_expression(&f.replacement)?;
18855        self.write_space();
18856        self.write_keyword("FROM");
18857        self.write_space();
18858        self.generate_expression(&f.from)?;
18859        if let Some(length) = &f.length {
18860            self.write_space();
18861            self.write_keyword("FOR");
18862            self.write_space();
18863            self.generate_expression(length)?;
18864        }
18865        self.write(")");
18866        Ok(())
18867    }
18868
18869    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18870        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18871        // when no characters are specified (PostgreSQL style)
18872        if f.position_explicit && f.characters.is_none() {
18873            match f.position {
18874                TrimPosition::Leading => {
18875                    self.write_keyword("LTRIM");
18876                    self.write("(");
18877                    self.generate_expression(&f.this)?;
18878                    self.write(")");
18879                    return Ok(());
18880                }
18881                TrimPosition::Trailing => {
18882                    self.write_keyword("RTRIM");
18883                    self.write("(");
18884                    self.generate_expression(&f.this)?;
18885                    self.write(")");
18886                    return Ok(());
18887                }
18888                TrimPosition::Both => {
18889                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18890                    // Fall through to standard TRIM handling
18891                }
18892            }
18893        }
18894
18895        self.write_keyword("TRIM");
18896        self.write("(");
18897        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18898        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18899        let force_standard = f.characters.is_some()
18900            && !f.sql_standard_syntax
18901            && matches!(
18902                self.config.dialect,
18903                Some(DialectType::Hive)
18904                    | Some(DialectType::Spark)
18905                    | Some(DialectType::Databricks)
18906                    | Some(DialectType::ClickHouse)
18907            );
18908        let use_standard = (f.sql_standard_syntax || force_standard)
18909            && !(f.position_explicit
18910                && f.characters.is_none()
18911                && matches!(f.position, TrimPosition::Both));
18912        if use_standard {
18913            // SQL standard syntax: TRIM(BOTH chars FROM str)
18914            // Only output position if it was explicitly specified
18915            if f.position_explicit {
18916                match f.position {
18917                    TrimPosition::Both => self.write_keyword("BOTH"),
18918                    TrimPosition::Leading => self.write_keyword("LEADING"),
18919                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18920                }
18921                self.write_space();
18922            }
18923            if let Some(chars) = &f.characters {
18924                self.generate_expression(chars)?;
18925                self.write_space();
18926            }
18927            self.write_keyword("FROM");
18928            self.write_space();
18929            self.generate_expression(&f.this)?;
18930        } else {
18931            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18932            self.generate_expression(&f.this)?;
18933            if let Some(chars) = &f.characters {
18934                self.write(", ");
18935                self.generate_expression(chars)?;
18936            }
18937        }
18938        self.write(")");
18939        Ok(())
18940    }
18941
18942    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18943        self.write_keyword("REPLACE");
18944        self.write("(");
18945        self.generate_expression(&f.this)?;
18946        self.write(", ");
18947        self.generate_expression(&f.old)?;
18948        self.write(", ");
18949        self.generate_expression(&f.new)?;
18950        self.write(")");
18951        Ok(())
18952    }
18953
18954    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18955        self.write_keyword(name);
18956        self.write("(");
18957        self.generate_expression(&f.this)?;
18958        self.write(", ");
18959        self.generate_expression(&f.length)?;
18960        self.write(")");
18961        Ok(())
18962    }
18963
18964    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18965        self.write_keyword("REPEAT");
18966        self.write("(");
18967        self.generate_expression(&f.this)?;
18968        self.write(", ");
18969        self.generate_expression(&f.times)?;
18970        self.write(")");
18971        Ok(())
18972    }
18973
18974    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18975        self.write_keyword(name);
18976        self.write("(");
18977        self.generate_expression(&f.this)?;
18978        self.write(", ");
18979        self.generate_expression(&f.length)?;
18980        if let Some(fill) = &f.fill {
18981            self.write(", ");
18982            self.generate_expression(fill)?;
18983        }
18984        self.write(")");
18985        Ok(())
18986    }
18987
18988    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18989        self.write_keyword("SPLIT");
18990        self.write("(");
18991        self.generate_expression(&f.this)?;
18992        self.write(", ");
18993        self.generate_expression(&f.delimiter)?;
18994        self.write(")");
18995        Ok(())
18996    }
18997
18998    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18999        use crate::dialects::DialectType;
19000        // PostgreSQL uses ~ operator for regex matching
19001        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19002            self.generate_expression(&f.this)?;
19003            self.write(" ~ ");
19004            self.generate_expression(&f.pattern)?;
19005        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19006            // Exasol uses REGEXP_LIKE as infix binary operator
19007            self.generate_expression(&f.this)?;
19008            self.write_keyword(" REGEXP_LIKE ");
19009            self.generate_expression(&f.pattern)?;
19010        } else if matches!(
19011            self.config.dialect,
19012            Some(DialectType::SingleStore)
19013                | Some(DialectType::Spark)
19014                | Some(DialectType::Hive)
19015                | Some(DialectType::Databricks)
19016        ) && f.flags.is_none()
19017        {
19018            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19019            self.generate_expression(&f.this)?;
19020            self.write_keyword(" RLIKE ");
19021            self.generate_expression(&f.pattern)?;
19022        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19023            // StarRocks uses REGEXP function syntax
19024            self.write_keyword("REGEXP");
19025            self.write("(");
19026            self.generate_expression(&f.this)?;
19027            self.write(", ");
19028            self.generate_expression(&f.pattern)?;
19029            if let Some(flags) = &f.flags {
19030                self.write(", ");
19031                self.generate_expression(flags)?;
19032            }
19033            self.write(")");
19034        } else {
19035            self.write_keyword("REGEXP_LIKE");
19036            self.write("(");
19037            self.generate_expression(&f.this)?;
19038            self.write(", ");
19039            self.generate_expression(&f.pattern)?;
19040            if let Some(flags) = &f.flags {
19041                self.write(", ");
19042                self.generate_expression(flags)?;
19043            }
19044            self.write(")");
19045        }
19046        Ok(())
19047    }
19048
19049    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19050        self.write_keyword("REGEXP_REPLACE");
19051        self.write("(");
19052        self.generate_expression(&f.this)?;
19053        self.write(", ");
19054        self.generate_expression(&f.pattern)?;
19055        self.write(", ");
19056        self.generate_expression(&f.replacement)?;
19057        if let Some(flags) = &f.flags {
19058            self.write(", ");
19059            self.generate_expression(flags)?;
19060        }
19061        self.write(")");
19062        Ok(())
19063    }
19064
19065    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19066        self.write_keyword("REGEXP_EXTRACT");
19067        self.write("(");
19068        self.generate_expression(&f.this)?;
19069        self.write(", ");
19070        self.generate_expression(&f.pattern)?;
19071        if let Some(group) = &f.group {
19072            self.write(", ");
19073            self.generate_expression(group)?;
19074        }
19075        self.write(")");
19076        Ok(())
19077    }
19078
19079    // Math function generators
19080
19081    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19082        self.write_keyword("ROUND");
19083        self.write("(");
19084        self.generate_expression(&f.this)?;
19085        if let Some(decimals) = &f.decimals {
19086            self.write(", ");
19087            self.generate_expression(decimals)?;
19088        }
19089        self.write(")");
19090        Ok(())
19091    }
19092
19093    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19094        self.write_keyword("FLOOR");
19095        self.write("(");
19096        self.generate_expression(&f.this)?;
19097        // Handle Druid-style FLOOR(time TO unit) syntax
19098        if let Some(to) = &f.to {
19099            self.write(" ");
19100            self.write_keyword("TO");
19101            self.write(" ");
19102            self.generate_expression(to)?;
19103        } else if let Some(scale) = &f.scale {
19104            self.write(", ");
19105            self.generate_expression(scale)?;
19106        }
19107        self.write(")");
19108        Ok(())
19109    }
19110
19111    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19112        self.write_keyword("CEIL");
19113        self.write("(");
19114        self.generate_expression(&f.this)?;
19115        // Handle Druid-style CEIL(time TO unit) syntax
19116        if let Some(to) = &f.to {
19117            self.write(" ");
19118            self.write_keyword("TO");
19119            self.write(" ");
19120            self.generate_expression(to)?;
19121        } else if let Some(decimals) = &f.decimals {
19122            self.write(", ");
19123            self.generate_expression(decimals)?;
19124        }
19125        self.write(")");
19126        Ok(())
19127    }
19128
19129    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19130        use crate::expressions::Literal;
19131
19132        if let Some(base) = &f.base {
19133            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19134            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19135            if self.is_log_base_none() {
19136                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19137                {
19138                    self.write_func_name("LOG2");
19139                    self.write("(");
19140                    self.generate_expression(&f.this)?;
19141                    self.write(")");
19142                    return Ok(());
19143                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19144                {
19145                    self.write_func_name("LOG10");
19146                    self.write("(");
19147                    self.generate_expression(&f.this)?;
19148                    self.write(")");
19149                    return Ok(());
19150                }
19151                // Other bases: fall through to LOG(base, value) — best effort
19152            }
19153
19154            self.write_func_name("LOG");
19155            self.write("(");
19156            if self.is_log_value_first() {
19157                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19158                self.generate_expression(&f.this)?;
19159                self.write(", ");
19160                self.generate_expression(base)?;
19161            } else {
19162                // Default (PostgreSQL, etc.): LOG(base, value)
19163                self.generate_expression(base)?;
19164                self.write(", ");
19165                self.generate_expression(&f.this)?;
19166            }
19167            self.write(")");
19168        } else {
19169            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19170            self.write_func_name("LOG");
19171            self.write("(");
19172            self.generate_expression(&f.this)?;
19173            self.write(")");
19174        }
19175        Ok(())
19176    }
19177
19178    /// Whether the target dialect uses LOG(value, base) order (value first).
19179    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19180    fn is_log_value_first(&self) -> bool {
19181        use crate::dialects::DialectType;
19182        matches!(
19183            self.config.dialect,
19184            Some(DialectType::BigQuery)
19185                | Some(DialectType::TSQL)
19186                | Some(DialectType::Tableau)
19187                | Some(DialectType::Fabric)
19188        )
19189    }
19190
19191    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19192    /// Presto, Trino, ClickHouse, Athena.
19193    fn is_log_base_none(&self) -> bool {
19194        use crate::dialects::DialectType;
19195        matches!(
19196            self.config.dialect,
19197            Some(DialectType::Presto)
19198                | Some(DialectType::Trino)
19199                | Some(DialectType::ClickHouse)
19200                | Some(DialectType::Athena)
19201        )
19202    }
19203
19204    // Date/time function generators
19205
19206    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19207        self.write_keyword("CURRENT_TIME");
19208        if let Some(precision) = f.precision {
19209            self.write(&format!("({})", precision));
19210        } else if matches!(
19211            self.config.dialect,
19212            Some(crate::dialects::DialectType::MySQL)
19213                | Some(crate::dialects::DialectType::SingleStore)
19214                | Some(crate::dialects::DialectType::TiDB)
19215        ) {
19216            self.write("()");
19217        }
19218        Ok(())
19219    }
19220
19221    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19222        use crate::dialects::DialectType;
19223
19224        // Oracle/Redshift SYSDATE handling
19225        if f.sysdate {
19226            match self.config.dialect {
19227                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19228                    self.write_keyword("SYSDATE");
19229                    return Ok(());
19230                }
19231                Some(DialectType::Snowflake) => {
19232                    // Snowflake uses SYSDATE() function
19233                    self.write_keyword("SYSDATE");
19234                    self.write("()");
19235                    return Ok(());
19236                }
19237                _ => {
19238                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19239                }
19240            }
19241        }
19242
19243        self.write_keyword("CURRENT_TIMESTAMP");
19244        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19245        if let Some(precision) = f.precision {
19246            self.write(&format!("({})", precision));
19247        } else if matches!(
19248            self.config.dialect,
19249            Some(crate::dialects::DialectType::MySQL)
19250                | Some(crate::dialects::DialectType::SingleStore)
19251                | Some(crate::dialects::DialectType::TiDB)
19252                | Some(crate::dialects::DialectType::Spark)
19253                | Some(crate::dialects::DialectType::Hive)
19254                | Some(crate::dialects::DialectType::Databricks)
19255                | Some(crate::dialects::DialectType::ClickHouse)
19256                | Some(crate::dialects::DialectType::BigQuery)
19257                | Some(crate::dialects::DialectType::Snowflake)
19258                | Some(crate::dialects::DialectType::Exasol)
19259        ) {
19260            self.write("()");
19261        }
19262        Ok(())
19263    }
19264
19265    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19266        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19267        if self.config.dialect == Some(DialectType::Exasol) {
19268            self.write_keyword("CONVERT_TZ");
19269            self.write("(");
19270            self.generate_expression(&f.this)?;
19271            self.write(", 'UTC', ");
19272            self.generate_expression(&f.zone)?;
19273            self.write(")");
19274            return Ok(());
19275        }
19276
19277        self.generate_expression(&f.this)?;
19278        self.write_space();
19279        self.write_keyword("AT TIME ZONE");
19280        self.write_space();
19281        self.generate_expression(&f.zone)?;
19282        Ok(())
19283    }
19284
19285    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19286        use crate::dialects::DialectType;
19287
19288        // Presto/Trino use DATE_ADD('unit', interval, date) format
19289        // with the interval cast to BIGINT when needed
19290        let is_presto_like = matches!(
19291            self.config.dialect,
19292            Some(DialectType::Presto) | Some(DialectType::Trino)
19293        );
19294
19295        if is_presto_like {
19296            self.write_keyword(name);
19297            self.write("(");
19298            // Unit as string literal
19299            self.write("'");
19300            self.write_simple_interval_unit(&f.unit, false);
19301            self.write("'");
19302            self.write(", ");
19303            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19304            let needs_cast = !self.returns_integer_type(&f.interval);
19305            if needs_cast {
19306                self.write_keyword("CAST");
19307                self.write("(");
19308            }
19309            self.generate_expression(&f.interval)?;
19310            if needs_cast {
19311                self.write_space();
19312                self.write_keyword("AS");
19313                self.write_space();
19314                self.write_keyword("BIGINT");
19315                self.write(")");
19316            }
19317            self.write(", ");
19318            self.generate_expression(&f.this)?;
19319            self.write(")");
19320        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19321            self.generate_expression(&f.this)?;
19322            self.write_space();
19323            if name.eq_ignore_ascii_case("DATE_SUB") {
19324                self.write("-");
19325            } else {
19326                self.write("+");
19327            }
19328            self.write_space();
19329            self.write_keyword("INTERVAL");
19330            self.write_space();
19331            self.write("'");
19332            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19333            let interval_sql = interval_gen.generate(&f.interval)?;
19334            self.write(&interval_sql);
19335            self.write(" ");
19336            self.write_simple_interval_unit(&f.unit, false);
19337            self.write("'");
19338        } else {
19339            self.write_keyword(name);
19340            self.write("(");
19341            self.generate_expression(&f.this)?;
19342            self.write(", ");
19343            self.write_keyword("INTERVAL");
19344            self.write_space();
19345            self.generate_expression(&f.interval)?;
19346            self.write_space();
19347            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19348            self.write(")");
19349        }
19350        Ok(())
19351    }
19352
19353    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19354    /// This is a heuristic to avoid full type inference
19355    fn returns_integer_type(&self, expr: &Expression) -> bool {
19356        use crate::expressions::{DataType, Literal};
19357        match expr {
19358            // Integer literals (no decimal point)
19359            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19360                let Literal::Number(n) = lit.as_ref() else {
19361                    unreachable!()
19362                };
19363                !n.contains('.')
19364            }
19365
19366            // FLOOR(x) returns integer if x is integer
19367            Expression::Floor(f) => self.returns_integer_type(&f.this),
19368
19369            // ROUND(x) returns integer if x is integer
19370            Expression::Round(f) => {
19371                // Only if no decimals arg or it's returning an integer
19372                f.decimals.is_none() && self.returns_integer_type(&f.this)
19373            }
19374
19375            // SIGN returns integer if input is integer
19376            Expression::Sign(f) => self.returns_integer_type(&f.this),
19377
19378            // ABS returns the same type as input
19379            Expression::Abs(f) => self.returns_integer_type(&f.this),
19380
19381            // Arithmetic operations on integers return integers
19382            Expression::Mul(op) => {
19383                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19384            }
19385            Expression::Add(op) => {
19386                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19387            }
19388            Expression::Sub(op) => {
19389                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19390            }
19391            Expression::Mod(op) => self.returns_integer_type(&op.left),
19392
19393            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19394            Expression::Cast(c) => matches!(
19395                &c.to,
19396                DataType::BigInt { .. }
19397                    | DataType::Int { .. }
19398                    | DataType::SmallInt { .. }
19399                    | DataType::TinyInt { .. }
19400            ),
19401
19402            // Negation: -x returns integer if x is integer
19403            Expression::Neg(op) => self.returns_integer_type(&op.this),
19404
19405            // Parenthesized expression
19406            Expression::Paren(p) => self.returns_integer_type(&p.this),
19407
19408            // Column references and most expressions are assumed to need casting
19409            // since we don't have full type information
19410            _ => false,
19411        }
19412    }
19413
19414    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19415        self.write_keyword("DATEDIFF");
19416        self.write("(");
19417        if let Some(unit) = &f.unit {
19418            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19419            self.write(", ");
19420        }
19421        self.generate_expression(&f.this)?;
19422        self.write(", ");
19423        self.generate_expression(&f.expression)?;
19424        self.write(")");
19425        Ok(())
19426    }
19427
19428    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19429        if self.config.dialect == Some(DialectType::ClickHouse) {
19430            self.write("dateTrunc");
19431        } else {
19432            self.write_keyword("DATE_TRUNC");
19433        }
19434        self.write("('");
19435        self.write_datetime_field(&f.unit);
19436        self.write("', ");
19437        self.generate_expression(&f.this)?;
19438        self.write(")");
19439        Ok(())
19440    }
19441
19442    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19443        use crate::dialects::DialectType;
19444        use crate::expressions::DateTimeField;
19445
19446        self.write_keyword("LAST_DAY");
19447        self.write("(");
19448        self.generate_expression(&f.this)?;
19449        if let Some(unit) = &f.unit {
19450            self.write(", ");
19451            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19452            // WEEK(SUNDAY) -> WEEK
19453            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19454                if let DateTimeField::WeekWithModifier(_) = unit {
19455                    self.write_keyword("WEEK");
19456                } else {
19457                    self.write_datetime_field(unit);
19458                }
19459            } else {
19460                self.write_datetime_field(unit);
19461            }
19462        }
19463        self.write(")");
19464        Ok(())
19465    }
19466
19467    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19468        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19469        if matches!(
19470            self.config.dialect,
19471            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19472        ) {
19473            self.write_keyword("DATEPART");
19474            self.write("(");
19475            self.write_datetime_field(&f.field);
19476            self.write(", ");
19477            self.generate_expression(&f.this)?;
19478            self.write(")");
19479            return Ok(());
19480        }
19481        self.write_keyword("EXTRACT");
19482        self.write("(");
19483        // Hive/Spark use lowercase datetime fields in EXTRACT
19484        if matches!(
19485            self.config.dialect,
19486            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19487        ) {
19488            self.write_datetime_field_lower(&f.field);
19489        } else {
19490            self.write_datetime_field(&f.field);
19491        }
19492        self.write_space();
19493        self.write_keyword("FROM");
19494        self.write_space();
19495        self.generate_expression(&f.this)?;
19496        self.write(")");
19497        Ok(())
19498    }
19499
19500    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19501        self.write_keyword("TO_DATE");
19502        self.write("(");
19503        self.generate_expression(&f.this)?;
19504        if let Some(format) = &f.format {
19505            self.write(", ");
19506            self.generate_expression(format)?;
19507        }
19508        self.write(")");
19509        Ok(())
19510    }
19511
19512    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19513        self.write_keyword("TO_TIMESTAMP");
19514        self.write("(");
19515        self.generate_expression(&f.this)?;
19516        if let Some(format) = &f.format {
19517            self.write(", ");
19518            self.generate_expression(format)?;
19519        }
19520        self.write(")");
19521        Ok(())
19522    }
19523
19524    // Control flow function generators
19525
19526    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19527        use crate::dialects::DialectType;
19528
19529        // Generic mode: normalize IF to CASE WHEN
19530        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19531            self.write_keyword("CASE WHEN");
19532            self.write_space();
19533            self.generate_expression(&f.condition)?;
19534            self.write_space();
19535            self.write_keyword("THEN");
19536            self.write_space();
19537            self.generate_expression(&f.true_value)?;
19538            if let Some(false_val) = &f.false_value {
19539                self.write_space();
19540                self.write_keyword("ELSE");
19541                self.write_space();
19542                self.generate_expression(false_val)?;
19543            }
19544            self.write_space();
19545            self.write_keyword("END");
19546            return Ok(());
19547        }
19548
19549        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19550        if self.config.dialect == Some(DialectType::Exasol) {
19551            self.write_keyword("IF");
19552            self.write_space();
19553            self.generate_expression(&f.condition)?;
19554            self.write_space();
19555            self.write_keyword("THEN");
19556            self.write_space();
19557            self.generate_expression(&f.true_value)?;
19558            if let Some(false_val) = &f.false_value {
19559                self.write_space();
19560                self.write_keyword("ELSE");
19561                self.write_space();
19562                self.generate_expression(false_val)?;
19563            }
19564            self.write_space();
19565            self.write_keyword("ENDIF");
19566            return Ok(());
19567        }
19568
19569        // Choose function name based on target dialect
19570        let func_name = match self.config.dialect {
19571            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
19572            Some(DialectType::Snowflake) => "IFF",
19573            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19574            Some(DialectType::Drill) => "`IF`",
19575            _ => "IF",
19576        };
19577        self.write(func_name);
19578        self.write("(");
19579        self.generate_expression(&f.condition)?;
19580        self.write(", ");
19581        self.generate_expression(&f.true_value)?;
19582        if let Some(false_val) = &f.false_value {
19583            self.write(", ");
19584            self.generate_expression(false_val)?;
19585        }
19586        self.write(")");
19587        Ok(())
19588    }
19589
19590    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19591        self.write_keyword("NVL2");
19592        self.write("(");
19593        self.generate_expression(&f.this)?;
19594        self.write(", ");
19595        self.generate_expression(&f.true_value)?;
19596        self.write(", ");
19597        self.generate_expression(&f.false_value)?;
19598        self.write(")");
19599        Ok(())
19600    }
19601
19602    // Typed aggregate function generators
19603
19604    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19605        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19606        let count_name = match self.config.normalize_functions {
19607            NormalizeFunctions::Upper => "COUNT".to_string(),
19608            NormalizeFunctions::Lower => "count".to_string(),
19609            NormalizeFunctions::None => f
19610                .original_name
19611                .clone()
19612                .unwrap_or_else(|| "COUNT".to_string()),
19613        };
19614        self.write(&count_name);
19615        self.write("(");
19616        if f.distinct {
19617            self.write_keyword("DISTINCT");
19618            self.write_space();
19619        }
19620        if f.star {
19621            self.write("*");
19622        } else if let Some(ref expr) = f.this {
19623            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19624            if let Expression::Tuple(tuple) = expr {
19625                // Check if we need to transform multi-arg COUNT DISTINCT
19626                // When dialect doesn't support multi_arg_distinct, transform:
19627                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19628                let needs_transform =
19629                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19630
19631                if needs_transform {
19632                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19633                    self.write_keyword("CASE");
19634                    for e in &tuple.expressions {
19635                        self.write_space();
19636                        self.write_keyword("WHEN");
19637                        self.write_space();
19638                        self.generate_expression(e)?;
19639                        self.write_space();
19640                        self.write_keyword("IS NULL THEN NULL");
19641                    }
19642                    self.write_space();
19643                    self.write_keyword("ELSE");
19644                    self.write(" (");
19645                    for (i, e) in tuple.expressions.iter().enumerate() {
19646                        if i > 0 {
19647                            self.write(", ");
19648                        }
19649                        self.generate_expression(e)?;
19650                    }
19651                    self.write(")");
19652                    self.write_space();
19653                    self.write_keyword("END");
19654                } else {
19655                    for (i, e) in tuple.expressions.iter().enumerate() {
19656                        if i > 0 {
19657                            self.write(", ");
19658                        }
19659                        self.generate_expression(e)?;
19660                    }
19661                }
19662            } else {
19663                self.generate_expression(expr)?;
19664            }
19665        }
19666        let clickhouse_ignore_nulls_outside =
19667            matches!(self.config.dialect, Some(DialectType::ClickHouse));
19668        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
19669            self.write_space();
19670            if ignore {
19671                self.write_keyword("IGNORE NULLS");
19672            } else {
19673                self.write_keyword("RESPECT NULLS");
19674            }
19675        }
19676        self.write(")");
19677        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
19678            self.write_space();
19679            if ignore {
19680                self.write_keyword("IGNORE NULLS");
19681            } else {
19682                self.write_keyword("RESPECT NULLS");
19683            }
19684        }
19685        if let Some(ref filter) = f.filter {
19686            self.write_space();
19687            self.write_keyword("FILTER");
19688            self.write("(");
19689            self.write_keyword("WHERE");
19690            self.write_space();
19691            self.generate_expression(filter)?;
19692            self.write(")");
19693        }
19694        Ok(())
19695    }
19696
19697    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19698        // Apply function name normalization based on config
19699        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19700            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19701            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19702            NormalizeFunctions::None => {
19703                // Use the original function name from parsing if available,
19704                // otherwise fall back to lowercase of the hardcoded constant
19705                if let Some(ref original) = f.name {
19706                    Cow::Owned(original.clone())
19707                } else {
19708                    Cow::Owned(name.to_ascii_lowercase())
19709                }
19710            }
19711        };
19712        self.write(func_name.as_ref());
19713        self.write("(");
19714        if f.distinct {
19715            self.write_keyword("DISTINCT");
19716            self.write_space();
19717        }
19718        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
19719        // Other aggregates may legitimately receive NULL as an explicit argument.
19720        let is_zero_arg_mode =
19721            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
19722        if !is_zero_arg_mode {
19723            self.generate_expression(&f.this)?;
19724        }
19725        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19726        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19727        if self.config.ignore_nulls_in_func
19728            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19729        {
19730            match f.ignore_nulls {
19731                Some(true) => {
19732                    self.write_space();
19733                    self.write_keyword("IGNORE NULLS");
19734                }
19735                Some(false) => {
19736                    self.write_space();
19737                    self.write_keyword("RESPECT NULLS");
19738                }
19739                None => {}
19740            }
19741        }
19742        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19743        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19744        if let Some((ref expr, is_max)) = f.having_max {
19745            self.write_space();
19746            self.write_keyword("HAVING");
19747            self.write_space();
19748            if is_max {
19749                self.write_keyword("MAX");
19750            } else {
19751                self.write_keyword("MIN");
19752            }
19753            self.write_space();
19754            self.generate_expression(expr)?;
19755        }
19756        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19757        if !f.order_by.is_empty() {
19758            self.write_space();
19759            self.write_keyword("ORDER BY");
19760            self.write_space();
19761            for (i, ord) in f.order_by.iter().enumerate() {
19762                if i > 0 {
19763                    self.write(", ");
19764                }
19765                self.generate_ordered(ord)?;
19766            }
19767        }
19768        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19769        if let Some(ref limit) = f.limit {
19770            self.write_space();
19771            self.write_keyword("LIMIT");
19772            self.write_space();
19773            // Check if this is a Tuple representing LIMIT offset, count
19774            if let Expression::Tuple(t) = limit.as_ref() {
19775                if t.expressions.len() == 2 {
19776                    self.generate_expression(&t.expressions[0])?;
19777                    self.write(", ");
19778                    self.generate_expression(&t.expressions[1])?;
19779                } else {
19780                    self.generate_expression(limit)?;
19781                }
19782            } else {
19783                self.generate_expression(limit)?;
19784            }
19785        }
19786        self.write(")");
19787        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19788        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19789        if !self.config.ignore_nulls_in_func
19790            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19791        {
19792            match f.ignore_nulls {
19793                Some(true) => {
19794                    self.write_space();
19795                    self.write_keyword("IGNORE NULLS");
19796                }
19797                Some(false) => {
19798                    self.write_space();
19799                    self.write_keyword("RESPECT NULLS");
19800                }
19801                None => {}
19802            }
19803        }
19804        if let Some(ref filter) = f.filter {
19805            self.write_space();
19806            self.write_keyword("FILTER");
19807            self.write("(");
19808            self.write_keyword("WHERE");
19809            self.write_space();
19810            self.generate_expression(filter)?;
19811            self.write(")");
19812        }
19813        Ok(())
19814    }
19815
19816    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19817    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19818    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19819        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19820        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19821            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19822            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19823                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19824                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19825                NormalizeFunctions::None => {
19826                    if let Some(ref original) = f.name {
19827                        Cow::Owned(original.clone())
19828                    } else {
19829                        Cow::Owned(name.to_ascii_lowercase())
19830                    }
19831                }
19832            };
19833            self.write(func_name.as_ref());
19834            self.write("(");
19835            if f.distinct {
19836                self.write_keyword("DISTINCT");
19837                self.write_space();
19838            }
19839            if !matches!(f.this, Expression::Null(_)) {
19840                self.generate_expression(&f.this)?;
19841            }
19842            self.write(", ");
19843            self.write_keyword("TRUE");
19844            self.write(")");
19845            return Ok(());
19846        }
19847        self.generate_agg_func(name, f)
19848    }
19849
19850    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19851        self.write_keyword("GROUP_CONCAT");
19852        self.write("(");
19853        if f.distinct {
19854            self.write_keyword("DISTINCT");
19855            self.write_space();
19856        }
19857        self.generate_expression(&f.this)?;
19858        if let Some(ref order_by) = f.order_by {
19859            self.write_space();
19860            self.write_keyword("ORDER BY");
19861            self.write_space();
19862            for (i, ord) in order_by.iter().enumerate() {
19863                if i > 0 {
19864                    self.write(", ");
19865                }
19866                self.generate_ordered(ord)?;
19867            }
19868        }
19869        if let Some(ref sep) = f.separator {
19870            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19871            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19872            if matches!(
19873                self.config.dialect,
19874                Some(crate::dialects::DialectType::SQLite)
19875            ) {
19876                self.write(", ");
19877                self.generate_expression(sep)?;
19878            } else {
19879                self.write_space();
19880                self.write_keyword("SEPARATOR");
19881                self.write_space();
19882                self.generate_expression(sep)?;
19883            }
19884        }
19885        if let Some(ref limit) = f.limit {
19886            self.write_space();
19887            self.write_keyword("LIMIT");
19888            self.write_space();
19889            self.generate_expression(limit)?;
19890        }
19891        self.write(")");
19892        if let Some(ref filter) = f.filter {
19893            self.write_space();
19894            self.write_keyword("FILTER");
19895            self.write("(");
19896            self.write_keyword("WHERE");
19897            self.write_space();
19898            self.generate_expression(filter)?;
19899            self.write(")");
19900        }
19901        Ok(())
19902    }
19903
19904    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19905        let is_tsql = matches!(
19906            self.config.dialect,
19907            Some(crate::dialects::DialectType::TSQL)
19908        );
19909        self.write_keyword("STRING_AGG");
19910        self.write("(");
19911        if f.distinct {
19912            self.write_keyword("DISTINCT");
19913            self.write_space();
19914        }
19915        self.generate_expression(&f.this)?;
19916        if let Some(ref separator) = f.separator {
19917            self.write(", ");
19918            self.generate_expression(separator)?;
19919        }
19920        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19921        if !is_tsql {
19922            if let Some(ref order_by) = f.order_by {
19923                self.write_space();
19924                self.write_keyword("ORDER BY");
19925                self.write_space();
19926                for (i, ord) in order_by.iter().enumerate() {
19927                    if i > 0 {
19928                        self.write(", ");
19929                    }
19930                    self.generate_ordered(ord)?;
19931                }
19932            }
19933        }
19934        if let Some(ref limit) = f.limit {
19935            self.write_space();
19936            self.write_keyword("LIMIT");
19937            self.write_space();
19938            self.generate_expression(limit)?;
19939        }
19940        self.write(")");
19941        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19942        if is_tsql {
19943            if let Some(ref order_by) = f.order_by {
19944                self.write_space();
19945                self.write_keyword("WITHIN GROUP");
19946                self.write(" (");
19947                self.write_keyword("ORDER BY");
19948                self.write_space();
19949                for (i, ord) in order_by.iter().enumerate() {
19950                    if i > 0 {
19951                        self.write(", ");
19952                    }
19953                    self.generate_ordered(ord)?;
19954                }
19955                self.write(")");
19956            }
19957        }
19958        if let Some(ref filter) = f.filter {
19959            self.write_space();
19960            self.write_keyword("FILTER");
19961            self.write("(");
19962            self.write_keyword("WHERE");
19963            self.write_space();
19964            self.generate_expression(filter)?;
19965            self.write(")");
19966        }
19967        Ok(())
19968    }
19969
19970    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19971        use crate::dialects::DialectType;
19972        self.write_keyword("LISTAGG");
19973        self.write("(");
19974        if f.distinct {
19975            self.write_keyword("DISTINCT");
19976            self.write_space();
19977        }
19978        self.generate_expression(&f.this)?;
19979        if let Some(ref sep) = f.separator {
19980            self.write(", ");
19981            self.generate_expression(sep)?;
19982        } else if matches!(
19983            self.config.dialect,
19984            Some(DialectType::Trino) | Some(DialectType::Presto)
19985        ) {
19986            // Trino/Presto require explicit separator; default to ','
19987            self.write(", ','");
19988        }
19989        if let Some(ref overflow) = f.on_overflow {
19990            self.write_space();
19991            self.write_keyword("ON OVERFLOW");
19992            self.write_space();
19993            match overflow {
19994                ListAggOverflow::Error => self.write_keyword("ERROR"),
19995                ListAggOverflow::Truncate { filler, with_count } => {
19996                    self.write_keyword("TRUNCATE");
19997                    if let Some(ref fill) = filler {
19998                        self.write_space();
19999                        self.generate_expression(fill)?;
20000                    }
20001                    if *with_count {
20002                        self.write_space();
20003                        self.write_keyword("WITH COUNT");
20004                    } else {
20005                        self.write_space();
20006                        self.write_keyword("WITHOUT COUNT");
20007                    }
20008                }
20009            }
20010        }
20011        self.write(")");
20012        if let Some(ref order_by) = f.order_by {
20013            self.write_space();
20014            self.write_keyword("WITHIN GROUP");
20015            self.write(" (");
20016            self.write_keyword("ORDER BY");
20017            self.write_space();
20018            for (i, ord) in order_by.iter().enumerate() {
20019                if i > 0 {
20020                    self.write(", ");
20021                }
20022                self.generate_ordered(ord)?;
20023            }
20024            self.write(")");
20025        }
20026        if let Some(ref filter) = f.filter {
20027            self.write_space();
20028            self.write_keyword("FILTER");
20029            self.write("(");
20030            self.write_keyword("WHERE");
20031            self.write_space();
20032            self.generate_expression(filter)?;
20033            self.write(")");
20034        }
20035        Ok(())
20036    }
20037
20038    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20039        self.write_keyword("SUM_IF");
20040        self.write("(");
20041        self.generate_expression(&f.this)?;
20042        self.write(", ");
20043        self.generate_expression(&f.condition)?;
20044        self.write(")");
20045        if let Some(ref filter) = f.filter {
20046            self.write_space();
20047            self.write_keyword("FILTER");
20048            self.write("(");
20049            self.write_keyword("WHERE");
20050            self.write_space();
20051            self.generate_expression(filter)?;
20052            self.write(")");
20053        }
20054        Ok(())
20055    }
20056
20057    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20058        self.write_keyword("APPROX_PERCENTILE");
20059        self.write("(");
20060        self.generate_expression(&f.this)?;
20061        self.write(", ");
20062        self.generate_expression(&f.percentile)?;
20063        if let Some(ref acc) = f.accuracy {
20064            self.write(", ");
20065            self.generate_expression(acc)?;
20066        }
20067        self.write(")");
20068        if let Some(ref filter) = f.filter {
20069            self.write_space();
20070            self.write_keyword("FILTER");
20071            self.write("(");
20072            self.write_keyword("WHERE");
20073            self.write_space();
20074            self.generate_expression(filter)?;
20075            self.write(")");
20076        }
20077        Ok(())
20078    }
20079
20080    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20081        self.write_keyword(name);
20082        self.write("(");
20083        self.generate_expression(&f.percentile)?;
20084        self.write(")");
20085        if let Some(ref order_by) = f.order_by {
20086            self.write_space();
20087            self.write_keyword("WITHIN GROUP");
20088            self.write(" (");
20089            self.write_keyword("ORDER BY");
20090            self.write_space();
20091            self.generate_expression(&f.this)?;
20092            for ord in order_by.iter() {
20093                if ord.desc {
20094                    self.write_space();
20095                    self.write_keyword("DESC");
20096                }
20097            }
20098            self.write(")");
20099        }
20100        if let Some(ref filter) = f.filter {
20101            self.write_space();
20102            self.write_keyword("FILTER");
20103            self.write("(");
20104            self.write_keyword("WHERE");
20105            self.write_space();
20106            self.generate_expression(filter)?;
20107            self.write(")");
20108        }
20109        Ok(())
20110    }
20111
20112    // Window function generators
20113
20114    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20115        self.write_keyword("NTILE");
20116        self.write("(");
20117        if let Some(num_buckets) = &f.num_buckets {
20118            self.generate_expression(num_buckets)?;
20119        }
20120        if let Some(order_by) = &f.order_by {
20121            self.write_keyword(" ORDER BY ");
20122            for (i, ob) in order_by.iter().enumerate() {
20123                if i > 0 {
20124                    self.write(", ");
20125                }
20126                self.generate_ordered(ob)?;
20127            }
20128        }
20129        self.write(")");
20130        Ok(())
20131    }
20132
20133    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20134        self.write_keyword(name);
20135        self.write("(");
20136        self.generate_expression(&f.this)?;
20137        if let Some(ref offset) = f.offset {
20138            self.write(", ");
20139            self.generate_expression(offset)?;
20140            if let Some(ref default) = f.default {
20141                self.write(", ");
20142                self.generate_expression(default)?;
20143            }
20144        }
20145        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20146        if self.config.ignore_nulls_in_func {
20147            match f.ignore_nulls {
20148                Some(true) => {
20149                    self.write_space();
20150                    self.write_keyword("IGNORE NULLS");
20151                }
20152                Some(false) => {
20153                    self.write_space();
20154                    self.write_keyword("RESPECT NULLS");
20155                }
20156                None => {}
20157            }
20158        }
20159        self.write(")");
20160        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20161        if !self.config.ignore_nulls_in_func {
20162            match f.ignore_nulls {
20163                Some(true) => {
20164                    self.write_space();
20165                    self.write_keyword("IGNORE NULLS");
20166                }
20167                Some(false) => {
20168                    self.write_space();
20169                    self.write_keyword("RESPECT NULLS");
20170                }
20171                None => {}
20172            }
20173        }
20174        Ok(())
20175    }
20176
20177    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20178        self.write_keyword(name);
20179        self.write("(");
20180        self.generate_expression(&f.this)?;
20181        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20182        if !f.order_by.is_empty() {
20183            self.write_space();
20184            self.write_keyword("ORDER BY");
20185            self.write_space();
20186            for (i, ordered) in f.order_by.iter().enumerate() {
20187                if i > 0 {
20188                    self.write(", ");
20189                }
20190                self.generate_ordered(ordered)?;
20191            }
20192        }
20193        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20194        if self.config.ignore_nulls_in_func {
20195            match f.ignore_nulls {
20196                Some(true) => {
20197                    self.write_space();
20198                    self.write_keyword("IGNORE NULLS");
20199                }
20200                Some(false) => {
20201                    self.write_space();
20202                    self.write_keyword("RESPECT NULLS");
20203                }
20204                None => {}
20205            }
20206        }
20207        self.write(")");
20208        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20209        if !self.config.ignore_nulls_in_func {
20210            match f.ignore_nulls {
20211                Some(true) => {
20212                    self.write_space();
20213                    self.write_keyword("IGNORE NULLS");
20214                }
20215                Some(false) => {
20216                    self.write_space();
20217                    self.write_keyword("RESPECT NULLS");
20218                }
20219                None => {}
20220            }
20221        }
20222        Ok(())
20223    }
20224
20225    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20226    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20227    fn generate_value_func_with_ignore_nulls_bool(
20228        &mut self,
20229        name: &str,
20230        f: &ValueFunc,
20231    ) -> Result<()> {
20232        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20233            self.write_keyword(name);
20234            self.write("(");
20235            self.generate_expression(&f.this)?;
20236            self.write(", ");
20237            self.write_keyword("TRUE");
20238            self.write(")");
20239            return Ok(());
20240        }
20241        self.generate_value_func(name, f)
20242    }
20243
20244    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20245        self.write_keyword("NTH_VALUE");
20246        self.write("(");
20247        self.generate_expression(&f.this)?;
20248        self.write(", ");
20249        self.generate_expression(&f.offset)?;
20250        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20251        if self.config.ignore_nulls_in_func {
20252            match f.ignore_nulls {
20253                Some(true) => {
20254                    self.write_space();
20255                    self.write_keyword("IGNORE NULLS");
20256                }
20257                Some(false) => {
20258                    self.write_space();
20259                    self.write_keyword("RESPECT NULLS");
20260                }
20261                None => {}
20262            }
20263        }
20264        self.write(")");
20265        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20266        if matches!(
20267            self.config.dialect,
20268            Some(crate::dialects::DialectType::Snowflake)
20269        ) {
20270            match f.from_first {
20271                Some(true) => {
20272                    self.write_space();
20273                    self.write_keyword("FROM FIRST");
20274                }
20275                Some(false) => {
20276                    self.write_space();
20277                    self.write_keyword("FROM LAST");
20278                }
20279                None => {}
20280            }
20281        }
20282        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20283        if !self.config.ignore_nulls_in_func {
20284            match f.ignore_nulls {
20285                Some(true) => {
20286                    self.write_space();
20287                    self.write_keyword("IGNORE NULLS");
20288                }
20289                Some(false) => {
20290                    self.write_space();
20291                    self.write_keyword("RESPECT NULLS");
20292                }
20293                None => {}
20294            }
20295        }
20296        Ok(())
20297    }
20298
20299    // Additional string function generators
20300
20301    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20302        // Standard syntax: POSITION(substr IN str)
20303        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20304        if matches!(
20305            self.config.dialect,
20306            Some(crate::dialects::DialectType::ClickHouse)
20307        ) {
20308            self.write_keyword("POSITION");
20309            self.write("(");
20310            self.generate_expression(&f.string)?;
20311            self.write(", ");
20312            self.generate_expression(&f.substring)?;
20313            if let Some(ref start) = f.start {
20314                self.write(", ");
20315                self.generate_expression(start)?;
20316            }
20317            self.write(")");
20318            return Ok(());
20319        }
20320
20321        self.write_keyword("POSITION");
20322        self.write("(");
20323        self.generate_expression(&f.substring)?;
20324        self.write_space();
20325        self.write_keyword("IN");
20326        self.write_space();
20327        self.generate_expression(&f.string)?;
20328        if let Some(ref start) = f.start {
20329            self.write(", ");
20330            self.generate_expression(start)?;
20331        }
20332        self.write(")");
20333        Ok(())
20334    }
20335
20336    // Additional math function generators
20337
20338    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20339        // Teradata RANDOM(lower, upper)
20340        if f.lower.is_some() || f.upper.is_some() {
20341            self.write_keyword("RANDOM");
20342            self.write("(");
20343            if let Some(ref lower) = f.lower {
20344                self.generate_expression(lower)?;
20345            }
20346            if let Some(ref upper) = f.upper {
20347                self.write(", ");
20348                self.generate_expression(upper)?;
20349            }
20350            self.write(")");
20351            return Ok(());
20352        }
20353        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20354        let func_name = match self.config.dialect {
20355            Some(crate::dialects::DialectType::Snowflake)
20356            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20357            _ => "RAND",
20358        };
20359        self.write_keyword(func_name);
20360        self.write("(");
20361        // DuckDB doesn't support seeded RANDOM, so skip the seed
20362        if !matches!(
20363            self.config.dialect,
20364            Some(crate::dialects::DialectType::DuckDB)
20365        ) {
20366            if let Some(ref seed) = f.seed {
20367                self.generate_expression(seed)?;
20368            }
20369        }
20370        self.write(")");
20371        Ok(())
20372    }
20373
20374    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20375        self.write_keyword("TRUNCATE");
20376        self.write("(");
20377        self.generate_expression(&f.this)?;
20378        if let Some(ref decimals) = f.decimals {
20379            self.write(", ");
20380            self.generate_expression(decimals)?;
20381        }
20382        self.write(")");
20383        Ok(())
20384    }
20385
20386    // Control flow generators
20387
20388    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20389        self.write_keyword("DECODE");
20390        self.write("(");
20391        self.generate_expression(&f.this)?;
20392        for (search, result) in &f.search_results {
20393            self.write(", ");
20394            self.generate_expression(search)?;
20395            self.write(", ");
20396            self.generate_expression(result)?;
20397        }
20398        if let Some(ref default) = f.default {
20399            self.write(", ");
20400            self.generate_expression(default)?;
20401        }
20402        self.write(")");
20403        Ok(())
20404    }
20405
20406    // Date/time function generators
20407
20408    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20409        self.write_keyword(name);
20410        self.write("(");
20411        self.generate_expression(&f.this)?;
20412        self.write(", ");
20413        self.generate_expression(&f.format)?;
20414        self.write(")");
20415        Ok(())
20416    }
20417
20418    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20419        self.write_keyword("FROM_UNIXTIME");
20420        self.write("(");
20421        self.generate_expression(&f.this)?;
20422        if let Some(ref format) = f.format {
20423            self.write(", ");
20424            self.generate_expression(format)?;
20425        }
20426        self.write(")");
20427        Ok(())
20428    }
20429
20430    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20431        self.write_keyword("UNIX_TIMESTAMP");
20432        self.write("(");
20433        if let Some(ref expr) = f.this {
20434            self.generate_expression(expr)?;
20435            if let Some(ref format) = f.format {
20436                self.write(", ");
20437                self.generate_expression(format)?;
20438            }
20439        } else if matches!(
20440            self.config.dialect,
20441            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20442        ) {
20443            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20444            self.write_keyword("CURRENT_TIMESTAMP");
20445            self.write("()");
20446        }
20447        self.write(")");
20448        Ok(())
20449    }
20450
20451    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20452        self.write_keyword("MAKE_DATE");
20453        self.write("(");
20454        self.generate_expression(&f.year)?;
20455        self.write(", ");
20456        self.generate_expression(&f.month)?;
20457        self.write(", ");
20458        self.generate_expression(&f.day)?;
20459        self.write(")");
20460        Ok(())
20461    }
20462
20463    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20464        self.write_keyword("MAKE_TIMESTAMP");
20465        self.write("(");
20466        self.generate_expression(&f.year)?;
20467        self.write(", ");
20468        self.generate_expression(&f.month)?;
20469        self.write(", ");
20470        self.generate_expression(&f.day)?;
20471        self.write(", ");
20472        self.generate_expression(&f.hour)?;
20473        self.write(", ");
20474        self.generate_expression(&f.minute)?;
20475        self.write(", ");
20476        self.generate_expression(&f.second)?;
20477        if let Some(ref tz) = f.timezone {
20478            self.write(", ");
20479            self.generate_expression(tz)?;
20480        }
20481        self.write(")");
20482        Ok(())
20483    }
20484
20485    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20486    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20487        match expr {
20488            Expression::Struct(s) => {
20489                if s.fields.iter().all(|(name, _)| name.is_some()) {
20490                    Some(
20491                        s.fields
20492                            .iter()
20493                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20494                            .collect(),
20495                    )
20496                } else {
20497                    None
20498                }
20499            }
20500            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20501                // Check if all args are Alias (named fields)
20502                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20503                    Some(
20504                        f.args
20505                            .iter()
20506                            .filter_map(|a| {
20507                                if let Expression::Alias(alias) = a {
20508                                    Some(alias.alias.name.clone())
20509                                } else {
20510                                    None
20511                                }
20512                            })
20513                            .collect(),
20514                    )
20515                } else {
20516                    None
20517                }
20518            }
20519            _ => None,
20520        }
20521    }
20522
20523    /// Check if a struct expression has any unnamed fields
20524    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20525        match expr {
20526            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20527            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20528                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20529            }
20530            _ => false,
20531        }
20532    }
20533
20534    /// Get the field count of a struct expression
20535    fn struct_field_count(expr: &Expression) -> usize {
20536        match expr {
20537            Expression::Struct(s) => s.fields.len(),
20538            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20539            _ => 0,
20540        }
20541    }
20542
20543    /// Apply field names to an unnamed struct expression, producing a new expression with names
20544    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20545        match expr {
20546            Expression::Struct(s) => {
20547                let mut new_fields = Vec::with_capacity(s.fields.len());
20548                for (i, (name, value)) in s.fields.iter().enumerate() {
20549                    if name.is_none() && i < field_names.len() {
20550                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20551                    } else {
20552                        new_fields.push((name.clone(), value.clone()));
20553                    }
20554                }
20555                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20556            }
20557            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20558                let mut new_args = Vec::with_capacity(f.args.len());
20559                for (i, arg) in f.args.iter().enumerate() {
20560                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20561                        // Wrap the value in an Alias with the inherited name
20562                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20563                            this: arg.clone(),
20564                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20565                            column_aliases: Vec::new(),
20566                            alias_explicit_as: false,
20567                            alias_keyword: None,
20568                            pre_alias_comments: Vec::new(),
20569                            trailing_comments: Vec::new(),
20570                            inferred_type: None,
20571                        })));
20572                    } else {
20573                        new_args.push(arg.clone());
20574                    }
20575                }
20576                Expression::Function(Box::new(crate::expressions::Function {
20577                    name: f.name.clone(),
20578                    args: new_args,
20579                    distinct: f.distinct,
20580                    trailing_comments: f.trailing_comments.clone(),
20581                    use_bracket_syntax: f.use_bracket_syntax,
20582                    no_parens: f.no_parens,
20583                    quoted: f.quoted,
20584                    span: None,
20585                    inferred_type: None,
20586                }))
20587            }
20588            _ => expr.clone(),
20589        }
20590    }
20591
20592    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20593    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20594    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20595    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20596        let first = match expressions.first() {
20597            Some(e) => e,
20598            None => return expressions.to_vec(),
20599        };
20600
20601        let field_names = match Self::extract_struct_field_names(first) {
20602            Some(names) if !names.is_empty() => names,
20603            _ => return expressions.to_vec(),
20604        };
20605
20606        let mut result = Vec::with_capacity(expressions.len());
20607        for (idx, expr) in expressions.iter().enumerate() {
20608            if idx == 0 {
20609                result.push(expr.clone());
20610                continue;
20611            }
20612            // Check if this is a struct with unnamed fields that needs name propagation
20613            if Self::struct_field_count(expr) == field_names.len()
20614                && Self::struct_has_unnamed_fields(expr)
20615            {
20616                result.push(Self::apply_struct_field_names(expr, &field_names));
20617            } else {
20618                result.push(expr.clone());
20619            }
20620        }
20621        result
20622    }
20623
20624    // Array function generators
20625
20626    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20627        // Apply struct name inheritance for target dialects that need it
20628        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20629        let needs_inheritance = matches!(
20630            self.config.dialect,
20631            Some(DialectType::DuckDB)
20632                | Some(DialectType::Spark)
20633                | Some(DialectType::Databricks)
20634                | Some(DialectType::Hive)
20635                | Some(DialectType::Snowflake)
20636                | Some(DialectType::Presto)
20637                | Some(DialectType::Trino)
20638        );
20639        let propagated: Vec<Expression>;
20640        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20641            propagated = Self::inherit_struct_field_names(&f.expressions);
20642            &propagated
20643        } else {
20644            &f.expressions
20645        };
20646
20647        // Check if elements should be split onto multiple lines (pretty + too wide)
20648        let should_split = if self.config.pretty && !expressions.is_empty() {
20649            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20650            for expr in expressions {
20651                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20652                Arc::make_mut(&mut temp_gen.config).pretty = false;
20653                temp_gen.generate_expression(expr)?;
20654                expr_strings.push(temp_gen.output);
20655            }
20656            self.too_wide(&expr_strings)
20657        } else {
20658            false
20659        };
20660
20661        if f.bracket_notation {
20662            // For Spark/Databricks, use ARRAY(...) with parens
20663            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20664            // For others (DuckDB, Snowflake), use bare [...]
20665            let (open, close) = match self.config.dialect {
20666                None
20667                | Some(DialectType::Generic)
20668                | Some(DialectType::Spark)
20669                | Some(DialectType::Databricks)
20670                | Some(DialectType::Hive) => {
20671                    self.write_keyword("ARRAY");
20672                    ("(", ")")
20673                }
20674                Some(DialectType::Presto)
20675                | Some(DialectType::Trino)
20676                | Some(DialectType::PostgreSQL)
20677                | Some(DialectType::Redshift)
20678                | Some(DialectType::Materialize)
20679                | Some(DialectType::RisingWave)
20680                | Some(DialectType::CockroachDB) => {
20681                    self.write_keyword("ARRAY");
20682                    ("[", "]")
20683                }
20684                _ => ("[", "]"),
20685            };
20686            self.write(open);
20687            if should_split {
20688                self.write_newline();
20689                self.indent_level += 1;
20690                for (i, expr) in expressions.iter().enumerate() {
20691                    self.write_indent();
20692                    self.generate_expression(expr)?;
20693                    if i + 1 < expressions.len() {
20694                        self.write(",");
20695                    }
20696                    self.write_newline();
20697                }
20698                self.indent_level -= 1;
20699                self.write_indent();
20700            } else {
20701                for (i, expr) in expressions.iter().enumerate() {
20702                    if i > 0 {
20703                        self.write(", ");
20704                    }
20705                    self.generate_expression(expr)?;
20706                }
20707            }
20708            self.write(close);
20709        } else {
20710            // Use LIST keyword if that was the original syntax (DuckDB)
20711            if f.use_list_keyword {
20712                self.write_keyword("LIST");
20713            } else {
20714                self.write_keyword("ARRAY");
20715            }
20716            // For Spark/Hive, always use ARRAY(...) with parens
20717            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20718            let has_subquery = expressions
20719                .iter()
20720                .any(|e| matches!(e, Expression::Select(_)));
20721            let (open, close) = if matches!(
20722                self.config.dialect,
20723                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20724            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20725                && has_subquery)
20726            {
20727                ("(", ")")
20728            } else {
20729                ("[", "]")
20730            };
20731            self.write(open);
20732            if should_split {
20733                self.write_newline();
20734                self.indent_level += 1;
20735                for (i, expr) in expressions.iter().enumerate() {
20736                    self.write_indent();
20737                    self.generate_expression(expr)?;
20738                    if i + 1 < expressions.len() {
20739                        self.write(",");
20740                    }
20741                    self.write_newline();
20742                }
20743                self.indent_level -= 1;
20744                self.write_indent();
20745            } else {
20746                for (i, expr) in expressions.iter().enumerate() {
20747                    if i > 0 {
20748                        self.write(", ");
20749                    }
20750                    self.generate_expression(expr)?;
20751                }
20752            }
20753            self.write(close);
20754        }
20755        Ok(())
20756    }
20757
20758    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20759        self.write_keyword("ARRAY_SORT");
20760        self.write("(");
20761        self.generate_expression(&f.this)?;
20762        if let Some(ref comp) = f.comparator {
20763            self.write(", ");
20764            self.generate_expression(comp)?;
20765        }
20766        self.write(")");
20767        Ok(())
20768    }
20769
20770    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20771        self.write_keyword(name);
20772        self.write("(");
20773        self.generate_expression(&f.this)?;
20774        self.write(", ");
20775        self.generate_expression(&f.separator)?;
20776        if let Some(ref null_rep) = f.null_replacement {
20777            self.write(", ");
20778            self.generate_expression(null_rep)?;
20779        }
20780        self.write(")");
20781        Ok(())
20782    }
20783
20784    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20785        self.write_keyword("UNNEST");
20786        self.write("(");
20787        self.generate_expression(&f.this)?;
20788        for extra in &f.expressions {
20789            self.write(", ");
20790            self.generate_expression(extra)?;
20791        }
20792        self.write(")");
20793        if f.with_ordinality {
20794            self.write_space();
20795            if self.config.unnest_with_ordinality {
20796                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20797                self.write_keyword("WITH ORDINALITY");
20798            } else if f.offset_alias.is_some() {
20799                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20800                // Alias (if any) comes BEFORE WITH OFFSET
20801                if let Some(ref alias) = f.alias {
20802                    self.write_keyword("AS");
20803                    self.write_space();
20804                    self.generate_identifier(alias)?;
20805                    self.write_space();
20806                }
20807                self.write_keyword("WITH OFFSET");
20808                if let Some(ref offset_alias) = f.offset_alias {
20809                    self.write_space();
20810                    self.write_keyword("AS");
20811                    self.write_space();
20812                    self.generate_identifier(offset_alias)?;
20813                }
20814            } else {
20815                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20816                self.write_keyword("WITH OFFSET");
20817                if f.alias.is_none() {
20818                    self.write(" AS offset");
20819                }
20820            }
20821        }
20822        if let Some(ref alias) = f.alias {
20823            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20824            let should_add_alias = if !f.with_ordinality {
20825                true
20826            } else if self.config.unnest_with_ordinality {
20827                // Presto/Trino: alias comes after WITH ORDINALITY
20828                true
20829            } else if f.offset_alias.is_some() {
20830                // BigQuery expansion: alias already handled above
20831                false
20832            } else {
20833                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20834                true
20835            };
20836            if should_add_alias {
20837                self.write_space();
20838                self.write_keyword("AS");
20839                self.write_space();
20840                self.generate_identifier(alias)?;
20841            }
20842        }
20843        Ok(())
20844    }
20845
20846    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20847        self.write_keyword("FILTER");
20848        self.write("(");
20849        self.generate_expression(&f.this)?;
20850        self.write(", ");
20851        self.generate_expression(&f.filter)?;
20852        self.write(")");
20853        Ok(())
20854    }
20855
20856    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20857        self.write_keyword("TRANSFORM");
20858        self.write("(");
20859        self.generate_expression(&f.this)?;
20860        self.write(", ");
20861        self.generate_expression(&f.transform)?;
20862        self.write(")");
20863        Ok(())
20864    }
20865
20866    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20867        self.write_keyword(name);
20868        self.write("(");
20869        self.generate_expression(&f.start)?;
20870        self.write(", ");
20871        self.generate_expression(&f.stop)?;
20872        if let Some(ref step) = f.step {
20873            self.write(", ");
20874            self.generate_expression(step)?;
20875        }
20876        self.write(")");
20877        Ok(())
20878    }
20879
20880    // Struct function generators
20881
20882    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20883        self.write_keyword("STRUCT");
20884        self.write("(");
20885        for (i, (name, expr)) in f.fields.iter().enumerate() {
20886            if i > 0 {
20887                self.write(", ");
20888            }
20889            if let Some(ref id) = name {
20890                self.generate_identifier(id)?;
20891                self.write(" ");
20892                self.write_keyword("AS");
20893                self.write(" ");
20894            }
20895            self.generate_expression(expr)?;
20896        }
20897        self.write(")");
20898        Ok(())
20899    }
20900
20901    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20902    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20903        // Extract named/unnamed fields from function args
20904        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20905        let mut names: Vec<Option<String>> = Vec::new();
20906        let mut values: Vec<&Expression> = Vec::new();
20907        let mut all_named = true;
20908
20909        for arg in &func.args {
20910            match arg {
20911                Expression::Alias(a) => {
20912                    names.push(Some(a.alias.name.clone()));
20913                    values.push(&a.this);
20914                }
20915                _ => {
20916                    names.push(None);
20917                    values.push(arg);
20918                    all_named = false;
20919                }
20920            }
20921        }
20922
20923        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20924            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20925            self.write("{");
20926            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20927                if i > 0 {
20928                    self.write(", ");
20929                }
20930                if let Some(n) = name {
20931                    self.write("'");
20932                    self.write(n);
20933                    self.write("'");
20934                } else {
20935                    self.write("'_");
20936                    self.write(&i.to_string());
20937                    self.write("'");
20938                }
20939                self.write(": ");
20940                self.generate_expression(value)?;
20941            }
20942            self.write("}");
20943            return Ok(());
20944        }
20945
20946        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20947            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20948            self.write_keyword("OBJECT_CONSTRUCT");
20949            self.write("(");
20950            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20951                if i > 0 {
20952                    self.write(", ");
20953                }
20954                if let Some(n) = name {
20955                    self.write("'");
20956                    self.write(n);
20957                    self.write("'");
20958                } else {
20959                    self.write("'_");
20960                    self.write(&i.to_string());
20961                    self.write("'");
20962                }
20963                self.write(", ");
20964                self.generate_expression(value)?;
20965            }
20966            self.write(")");
20967            return Ok(());
20968        }
20969
20970        if matches!(
20971            self.config.dialect,
20972            Some(DialectType::Presto) | Some(DialectType::Trino)
20973        ) {
20974            if all_named && !names.is_empty() {
20975                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20976                // Need to infer types from values
20977                self.write_keyword("CAST");
20978                self.write("(");
20979                self.write_keyword("ROW");
20980                self.write("(");
20981                for (i, value) in values.iter().enumerate() {
20982                    if i > 0 {
20983                        self.write(", ");
20984                    }
20985                    self.generate_expression(value)?;
20986                }
20987                self.write(")");
20988                self.write(" ");
20989                self.write_keyword("AS");
20990                self.write(" ");
20991                self.write_keyword("ROW");
20992                self.write("(");
20993                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20994                    if i > 0 {
20995                        self.write(", ");
20996                    }
20997                    if let Some(n) = name {
20998                        self.write(n);
20999                    }
21000                    self.write(" ");
21001                    let type_str = Self::infer_sql_type_for_presto(value);
21002                    self.write_keyword(&type_str);
21003                }
21004                self.write(")");
21005                self.write(")");
21006            } else {
21007                // Unnamed: ROW(values...)
21008                self.write_keyword("ROW");
21009                self.write("(");
21010                for (i, value) in values.iter().enumerate() {
21011                    if i > 0 {
21012                        self.write(", ");
21013                    }
21014                    self.generate_expression(value)?;
21015                }
21016                self.write(")");
21017            }
21018            return Ok(());
21019        }
21020
21021        // Default: ROW(values...) for other dialects
21022        self.write_keyword("ROW");
21023        self.write("(");
21024        for (i, value) in values.iter().enumerate() {
21025            if i > 0 {
21026                self.write(", ");
21027            }
21028            self.generate_expression(value)?;
21029        }
21030        self.write(")");
21031        Ok(())
21032    }
21033
21034    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21035    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21036        match expr {
21037            Expression::Literal(lit)
21038                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21039            {
21040                "VARCHAR".to_string()
21041            }
21042            Expression::Literal(lit)
21043                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21044            {
21045                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21046                    unreachable!()
21047                };
21048                if n.contains('.') {
21049                    "DOUBLE".to_string()
21050                } else {
21051                    "INTEGER".to_string()
21052                }
21053            }
21054            Expression::Boolean(_) => "BOOLEAN".to_string(),
21055            Expression::Literal(lit)
21056                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21057            {
21058                "DATE".to_string()
21059            }
21060            Expression::Literal(lit)
21061                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21062            {
21063                "TIMESTAMP".to_string()
21064            }
21065            Expression::Literal(lit)
21066                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21067            {
21068                "TIMESTAMP".to_string()
21069            }
21070            Expression::Array(_) | Expression::ArrayFunc(_) => {
21071                // Try to infer element type from first element
21072                "ARRAY(VARCHAR)".to_string()
21073            }
21074            // For nested structs - generate a nested ROW type by inspecting fields
21075            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21076            Expression::Function(f) => {
21077                if f.name.eq_ignore_ascii_case("STRUCT") {
21078                    "ROW".to_string()
21079                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21080                    "DATE".to_string()
21081                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21082                    || f.name.eq_ignore_ascii_case("NOW")
21083                {
21084                    "TIMESTAMP".to_string()
21085                } else {
21086                    "VARCHAR".to_string()
21087                }
21088            }
21089            _ => "VARCHAR".to_string(),
21090        }
21091    }
21092
21093    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21094        // DuckDB uses STRUCT_EXTRACT function syntax
21095        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21096            self.write_keyword("STRUCT_EXTRACT");
21097            self.write("(");
21098            self.generate_expression(&f.this)?;
21099            self.write(", ");
21100            // Output field name as string literal
21101            self.write("'");
21102            self.write(&f.field.name);
21103            self.write("'");
21104            self.write(")");
21105            return Ok(());
21106        }
21107        self.generate_expression(&f.this)?;
21108        self.write(".");
21109        self.generate_identifier(&f.field)
21110    }
21111
21112    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21113        if matches!(
21114            self.config.dialect,
21115            Some(DialectType::Spark | DialectType::Databricks)
21116        ) {
21117            self.write_keyword("STRUCT");
21118            self.write("(");
21119            for (i, (name, value)) in f.pairs.iter().enumerate() {
21120                if i > 0 {
21121                    self.write(", ");
21122                }
21123                self.generate_expression(value)?;
21124                self.write(" ");
21125                self.write_keyword("AS");
21126                self.write(" ");
21127                if let Expression::Literal(lit) = name {
21128                    if let Literal::String(field_name) = lit.as_ref() {
21129                        self.generate_identifier(&Identifier::new(field_name))?;
21130                    } else {
21131                        self.generate_expression(name)?;
21132                    }
21133                } else {
21134                    self.generate_expression(name)?;
21135                }
21136            }
21137            self.write(")");
21138            return Ok(());
21139        }
21140
21141        self.write_keyword("NAMED_STRUCT");
21142        self.write("(");
21143        for (i, (name, value)) in f.pairs.iter().enumerate() {
21144            if i > 0 {
21145                self.write(", ");
21146            }
21147            self.generate_expression(name)?;
21148            self.write(", ");
21149            self.generate_expression(value)?;
21150        }
21151        self.write(")");
21152        Ok(())
21153    }
21154
21155    // Map function generators
21156
21157    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21158        if f.curly_brace_syntax {
21159            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21160            if f.with_map_keyword {
21161                self.write_keyword("MAP");
21162                self.write(" ");
21163            }
21164            self.write("{");
21165            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21166                if i > 0 {
21167                    self.write(", ");
21168                }
21169                self.generate_expression(key)?;
21170                self.write(": ");
21171                self.generate_expression(val)?;
21172            }
21173            self.write("}");
21174        } else {
21175            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21176            self.write_keyword("MAP");
21177            self.write("(");
21178            self.write_keyword("ARRAY");
21179            self.write("[");
21180            for (i, key) in f.keys.iter().enumerate() {
21181                if i > 0 {
21182                    self.write(", ");
21183                }
21184                self.generate_expression(key)?;
21185            }
21186            self.write("], ");
21187            self.write_keyword("ARRAY");
21188            self.write("[");
21189            for (i, val) in f.values.iter().enumerate() {
21190                if i > 0 {
21191                    self.write(", ");
21192                }
21193                self.generate_expression(val)?;
21194            }
21195            self.write("])");
21196        }
21197        Ok(())
21198    }
21199
21200    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21201        self.write_keyword(name);
21202        self.write("(");
21203        self.generate_expression(&f.this)?;
21204        self.write(", ");
21205        self.generate_expression(&f.transform)?;
21206        self.write(")");
21207        Ok(())
21208    }
21209
21210    // JSON function generators
21211
21212    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21213        use crate::dialects::DialectType;
21214
21215        // Check if we should use arrow syntax (-> or ->>)
21216        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21217
21218        if use_arrow {
21219            // Output arrow syntax: expr -> path or expr ->> path
21220            self.generate_expression(&f.this)?;
21221            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21222                self.write(" ->> ");
21223            } else {
21224                self.write(" -> ");
21225            }
21226            self.generate_expression(&f.path)?;
21227            return Ok(());
21228        }
21229
21230        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21231        if f.hash_arrow_syntax
21232            && matches!(
21233                self.config.dialect,
21234                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21235            )
21236        {
21237            self.generate_expression(&f.this)?;
21238            self.write(" #>> ");
21239            self.generate_expression(&f.path)?;
21240            return Ok(());
21241        }
21242
21243        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21244        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21245        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21246            match name {
21247                "JSON_EXTRACT_SCALAR"
21248                | "JSON_EXTRACT_PATH_TEXT"
21249                | "JSON_EXTRACT"
21250                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21251                _ => name,
21252            }
21253        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21254            match name {
21255                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21256                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21257                _ => name,
21258            }
21259        } else {
21260            name
21261        };
21262
21263        self.write_keyword(func_name);
21264        self.write("(");
21265        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21266        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21267            if let Expression::Cast(ref cast) = f.this {
21268                if matches!(cast.to, crate::expressions::DataType::Json) {
21269                    self.generate_expression(&cast.this)?;
21270                } else {
21271                    self.generate_expression(&f.this)?;
21272                }
21273            } else {
21274                self.generate_expression(&f.this)?;
21275            }
21276        } else {
21277            self.generate_expression(&f.this)?;
21278        }
21279        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21280        // decompose JSON path into separate string arguments
21281        if matches!(
21282            self.config.dialect,
21283            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21284        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21285        {
21286            if let Expression::Literal(ref lit) = f.path {
21287                if let Literal::String(ref s) = lit.as_ref() {
21288                    let parts = Self::decompose_json_path(s);
21289                    for part in &parts {
21290                        self.write(", '");
21291                        self.write(part);
21292                        self.write("'");
21293                    }
21294                }
21295            } else {
21296                self.write(", ");
21297                self.generate_expression(&f.path)?;
21298            }
21299        } else {
21300            self.write(", ");
21301            self.generate_expression(&f.path)?;
21302        }
21303
21304        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21305        // These go BEFORE the closing parenthesis
21306        if let Some(ref wrapper) = f.wrapper_option {
21307            self.write_space();
21308            self.write_keyword(wrapper);
21309        }
21310        if let Some(ref quotes) = f.quotes_option {
21311            self.write_space();
21312            self.write_keyword(quotes);
21313            if f.on_scalar_string {
21314                self.write_space();
21315                self.write_keyword("ON SCALAR STRING");
21316            }
21317        }
21318        if let Some(ref on_err) = f.on_error {
21319            self.write_space();
21320            self.write_keyword(on_err);
21321        }
21322        if let Some(ref ret_type) = f.returning {
21323            self.write_space();
21324            self.write_keyword("RETURNING");
21325            self.write_space();
21326            self.generate_data_type(ret_type)?;
21327        }
21328
21329        self.write(")");
21330        Ok(())
21331    }
21332
21333    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21334    fn dialect_supports_json_arrow(&self) -> bool {
21335        use crate::dialects::DialectType;
21336        match self.config.dialect {
21337            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21338            Some(DialectType::PostgreSQL) => true,
21339            Some(DialectType::MySQL) => true,
21340            Some(DialectType::DuckDB) => true,
21341            Some(DialectType::CockroachDB) => true,
21342            Some(DialectType::StarRocks) => true,
21343            Some(DialectType::SQLite) => true,
21344            // Other dialects use function syntax
21345            _ => false,
21346        }
21347    }
21348
21349    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21350        use crate::dialects::DialectType;
21351
21352        // PostgreSQL uses #> operator for JSONB path extraction
21353        if matches!(
21354            self.config.dialect,
21355            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21356        ) && name == "JSON_EXTRACT_PATH"
21357        {
21358            self.generate_expression(&f.this)?;
21359            self.write(" #> ");
21360            if f.paths.len() == 1 {
21361                self.generate_expression(&f.paths[0])?;
21362            } else {
21363                // Multiple paths: ARRAY[path1, path2, ...]
21364                self.write_keyword("ARRAY");
21365                self.write("[");
21366                for (i, path) in f.paths.iter().enumerate() {
21367                    if i > 0 {
21368                        self.write(", ");
21369                    }
21370                    self.generate_expression(path)?;
21371                }
21372                self.write("]");
21373            }
21374            return Ok(());
21375        }
21376
21377        self.write_keyword(name);
21378        self.write("(");
21379        self.generate_expression(&f.this)?;
21380        for path in &f.paths {
21381            self.write(", ");
21382            self.generate_expression(path)?;
21383        }
21384        self.write(")");
21385        Ok(())
21386    }
21387
21388    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21389        use crate::dialects::DialectType;
21390
21391        self.write_keyword("JSON_OBJECT");
21392        self.write("(");
21393        if f.star {
21394            self.write("*");
21395        } else {
21396            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21397            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21398            // Also respect the json_key_value_pair_sep config
21399            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21400                || matches!(
21401                    self.config.dialect,
21402                    Some(DialectType::BigQuery)
21403                        | Some(DialectType::MySQL)
21404                        | Some(DialectType::SQLite)
21405                );
21406
21407            for (i, (key, value)) in f.pairs.iter().enumerate() {
21408                if i > 0 {
21409                    self.write(", ");
21410                }
21411                self.generate_expression(key)?;
21412                if use_comma_syntax {
21413                    self.write(", ");
21414                } else {
21415                    self.write(": ");
21416                }
21417                self.generate_expression(value)?;
21418            }
21419        }
21420        if let Some(null_handling) = f.null_handling {
21421            self.write_space();
21422            match null_handling {
21423                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21424                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21425            }
21426        }
21427        if f.with_unique_keys {
21428            self.write_space();
21429            self.write_keyword("WITH UNIQUE KEYS");
21430        }
21431        if let Some(ref ret_type) = f.returning_type {
21432            self.write_space();
21433            self.write_keyword("RETURNING");
21434            self.write_space();
21435            self.generate_data_type(ret_type)?;
21436            if f.format_json {
21437                self.write_space();
21438                self.write_keyword("FORMAT JSON");
21439            }
21440            if let Some(ref enc) = f.encoding {
21441                self.write_space();
21442                self.write_keyword("ENCODING");
21443                self.write_space();
21444                self.write(enc);
21445            }
21446        }
21447        self.write(")");
21448        Ok(())
21449    }
21450
21451    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21452        self.write_keyword(name);
21453        self.write("(");
21454        self.generate_expression(&f.this)?;
21455        for (path, value) in &f.path_values {
21456            self.write(", ");
21457            self.generate_expression(path)?;
21458            self.write(", ");
21459            self.generate_expression(value)?;
21460        }
21461        self.write(")");
21462        Ok(())
21463    }
21464
21465    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21466        self.write_keyword("JSON_ARRAYAGG");
21467        self.write("(");
21468        self.generate_expression(&f.this)?;
21469        if let Some(ref order_by) = f.order_by {
21470            self.write_space();
21471            self.write_keyword("ORDER BY");
21472            self.write_space();
21473            for (i, ord) in order_by.iter().enumerate() {
21474                if i > 0 {
21475                    self.write(", ");
21476                }
21477                self.generate_ordered(ord)?;
21478            }
21479        }
21480        if let Some(null_handling) = f.null_handling {
21481            self.write_space();
21482            match null_handling {
21483                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21484                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21485            }
21486        }
21487        self.write(")");
21488        if let Some(ref filter) = f.filter {
21489            self.write_space();
21490            self.write_keyword("FILTER");
21491            self.write("(");
21492            self.write_keyword("WHERE");
21493            self.write_space();
21494            self.generate_expression(filter)?;
21495            self.write(")");
21496        }
21497        Ok(())
21498    }
21499
21500    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21501        self.write_keyword("JSON_OBJECTAGG");
21502        self.write("(");
21503        self.generate_expression(&f.key)?;
21504        self.write(": ");
21505        self.generate_expression(&f.value)?;
21506        if let Some(null_handling) = f.null_handling {
21507            self.write_space();
21508            match null_handling {
21509                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21510                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21511            }
21512        }
21513        self.write(")");
21514        if let Some(ref filter) = f.filter {
21515            self.write_space();
21516            self.write_keyword("FILTER");
21517            self.write("(");
21518            self.write_keyword("WHERE");
21519            self.write_space();
21520            self.generate_expression(filter)?;
21521            self.write(")");
21522        }
21523        Ok(())
21524    }
21525
21526    // Type casting/conversion generators
21527
21528    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21529        use crate::dialects::DialectType;
21530
21531        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21532        if self.config.dialect == Some(DialectType::Redshift) {
21533            self.write_keyword("CAST");
21534            self.write("(");
21535            self.generate_expression(&f.this)?;
21536            self.write_space();
21537            self.write_keyword("AS");
21538            self.write_space();
21539            self.generate_data_type(&f.to)?;
21540            self.write(")");
21541            return Ok(());
21542        }
21543
21544        self.write_keyword("CONVERT");
21545        self.write("(");
21546        self.generate_data_type(&f.to)?;
21547        self.write(", ");
21548        self.generate_expression(&f.this)?;
21549        if let Some(ref style) = f.style {
21550            self.write(", ");
21551            self.generate_expression(style)?;
21552        }
21553        self.write(")");
21554        Ok(())
21555    }
21556
21557    // Additional expression generators
21558
21559    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21560        if f.colon {
21561            // DuckDB syntax: LAMBDA x : expr
21562            self.write_keyword("LAMBDA");
21563            self.write_space();
21564            for (i, param) in f.parameters.iter().enumerate() {
21565                if i > 0 {
21566                    self.write(", ");
21567                }
21568                self.generate_identifier(param)?;
21569            }
21570            self.write(" : ");
21571        } else {
21572            // Standard syntax: x -> expr or (x, y) -> expr
21573            if f.parameters.len() == 1 {
21574                self.generate_identifier(&f.parameters[0])?;
21575            } else {
21576                self.write("(");
21577                for (i, param) in f.parameters.iter().enumerate() {
21578                    if i > 0 {
21579                        self.write(", ");
21580                    }
21581                    self.generate_identifier(param)?;
21582                }
21583                self.write(")");
21584            }
21585            self.write(" -> ");
21586        }
21587        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21588            if let Expression::Lambda(inner) = &f.body {
21589                self.generate_lambda_with_parenthesized_single_param(inner)?;
21590                return Ok(());
21591            }
21592        }
21593
21594        self.generate_expression(&f.body)
21595    }
21596
21597    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
21598        if f.colon {
21599            return self.generate_lambda(f);
21600        }
21601
21602        self.write("(");
21603        for (i, param) in f.parameters.iter().enumerate() {
21604            if i > 0 {
21605                self.write(", ");
21606            }
21607            self.generate_identifier(param)?;
21608        }
21609        self.write(") -> ");
21610        self.generate_expression(&f.body)
21611    }
21612
21613    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21614        self.generate_identifier(&f.name)?;
21615        match f.separator {
21616            NamedArgSeparator::DArrow => self.write(" => "),
21617            NamedArgSeparator::ColonEq => self.write(" := "),
21618            NamedArgSeparator::Eq => self.write(" = "),
21619        }
21620        self.generate_expression(&f.value)
21621    }
21622
21623    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21624        self.write_keyword(&f.prefix);
21625        self.write(" ");
21626        self.generate_expression(&f.this)
21627    }
21628
21629    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21630        match f.style {
21631            ParameterStyle::Question => self.write("?"),
21632            ParameterStyle::Dollar => {
21633                self.write("$");
21634                if let Some(idx) = f.index {
21635                    self.write(&idx.to_string());
21636                } else if let Some(ref name) = f.name {
21637                    // Session variable like $x or $query_id
21638                    self.write(name);
21639                }
21640            }
21641            ParameterStyle::DollarBrace => {
21642                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21643                self.write("${");
21644                if let Some(ref name) = f.name {
21645                    self.write(name);
21646                }
21647                if let Some(ref expr) = f.expression {
21648                    self.write(":");
21649                    self.write(expr);
21650                }
21651                self.write("}");
21652            }
21653            ParameterStyle::Colon => {
21654                self.write(":");
21655                if let Some(idx) = f.index {
21656                    self.write(&idx.to_string());
21657                } else if let Some(ref name) = f.name {
21658                    self.write(name);
21659                }
21660            }
21661            ParameterStyle::At => {
21662                self.write("@");
21663                if let Some(ref name) = f.name {
21664                    if f.string_quoted {
21665                        self.write("'");
21666                        self.write(name);
21667                        self.write("'");
21668                    } else if f.quoted {
21669                        self.write("\"");
21670                        self.write(name);
21671                        self.write("\"");
21672                    } else {
21673                        self.write(name);
21674                    }
21675                }
21676            }
21677            ParameterStyle::DoubleAt => {
21678                self.write("@@");
21679                if let Some(ref name) = f.name {
21680                    self.write(name);
21681                }
21682            }
21683            ParameterStyle::DoubleDollar => {
21684                self.write("$$");
21685                if let Some(ref name) = f.name {
21686                    self.write(name);
21687                }
21688            }
21689            ParameterStyle::Percent => {
21690                if let Some(ref name) = f.name {
21691                    // %(name)s format
21692                    self.write("%(");
21693                    self.write(name);
21694                    self.write(")s");
21695                } else {
21696                    // %s format
21697                    self.write("%s");
21698                }
21699            }
21700            ParameterStyle::Brace => {
21701                // Spark/Databricks widget template variable: {name}
21702                // ClickHouse query parameter may include kind: {name: Type}
21703                self.write("{");
21704                if let Some(ref name) = f.name {
21705                    self.write(name);
21706                }
21707                if let Some(ref expr) = f.expression {
21708                    self.write(": ");
21709                    self.write(expr);
21710                }
21711                self.write("}");
21712            }
21713        }
21714        Ok(())
21715    }
21716
21717    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21718        self.write("?");
21719        if let Some(idx) = f.index {
21720            self.write(&idx.to_string());
21721        }
21722        Ok(())
21723    }
21724
21725    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21726        if f.is_block {
21727            self.write("/*");
21728            self.write(&f.text);
21729            self.write("*/");
21730        } else {
21731            self.write("--");
21732            self.write(&f.text);
21733        }
21734        Ok(())
21735    }
21736
21737    // Additional predicate generators
21738
21739    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21740        self.generate_expression(&f.this)?;
21741        if f.not {
21742            self.write_space();
21743            self.write_keyword("NOT");
21744        }
21745        self.write_space();
21746        self.write_keyword("SIMILAR TO");
21747        self.write_space();
21748        self.generate_expression(&f.pattern)?;
21749        if let Some(ref escape) = f.escape {
21750            self.write_space();
21751            self.write_keyword("ESCAPE");
21752            self.write_space();
21753            self.generate_expression(escape)?;
21754        }
21755        Ok(())
21756    }
21757
21758    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21759        self.generate_expression(&f.this)?;
21760        self.write_space();
21761        // Output comparison operator if present
21762        if let Some(op) = &f.op {
21763            match op {
21764                QuantifiedOp::Eq => self.write("="),
21765                QuantifiedOp::Neq => self.write("<>"),
21766                QuantifiedOp::Lt => self.write("<"),
21767                QuantifiedOp::Lte => self.write("<="),
21768                QuantifiedOp::Gt => self.write(">"),
21769                QuantifiedOp::Gte => self.write(">="),
21770            }
21771            self.write_space();
21772        }
21773        self.write_keyword(name);
21774
21775        // If the child is a Subquery, it provides its own parens — output with space
21776        if matches!(&f.subquery, Expression::Subquery(_)) {
21777            self.write_space();
21778            self.generate_expression(&f.subquery)?;
21779        } else {
21780            self.write("(");
21781
21782            let is_statement = matches!(
21783                &f.subquery,
21784                Expression::Select(_)
21785                    | Expression::Union(_)
21786                    | Expression::Intersect(_)
21787                    | Expression::Except(_)
21788            );
21789
21790            if self.config.pretty && is_statement {
21791                self.write_newline();
21792                self.indent_level += 1;
21793                self.write_indent();
21794            }
21795            self.generate_expression(&f.subquery)?;
21796            if self.config.pretty && is_statement {
21797                self.write_newline();
21798                self.indent_level -= 1;
21799                self.write_indent();
21800            }
21801            self.write(")");
21802        }
21803        Ok(())
21804    }
21805
21806    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21807        // Check if this is a simple binary form (this OVERLAPS expression)
21808        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21809            self.generate_expression(this)?;
21810            self.write_space();
21811            self.write_keyword("OVERLAPS");
21812            self.write_space();
21813            self.generate_expression(expr)?;
21814        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21815            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21816        {
21817            // Full ANSI form: (a, b) OVERLAPS (c, d)
21818            self.write("(");
21819            self.generate_expression(ls)?;
21820            self.write(", ");
21821            self.generate_expression(le)?;
21822            self.write(")");
21823            self.write_space();
21824            self.write_keyword("OVERLAPS");
21825            self.write_space();
21826            self.write("(");
21827            self.generate_expression(rs)?;
21828            self.write(", ");
21829            self.generate_expression(re)?;
21830            self.write(")");
21831        }
21832        Ok(())
21833    }
21834
21835    // Type conversion generators
21836
21837    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21838        use crate::dialects::DialectType;
21839
21840        // SingleStore uses !:> syntax for try cast
21841        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21842            self.generate_expression(&cast.this)?;
21843            self.write(" !:> ");
21844            self.generate_data_type(&cast.to)?;
21845            return Ok(());
21846        }
21847
21848        // Teradata uses TRYCAST (no underscore)
21849        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21850            self.write_keyword("TRYCAST");
21851            self.write("(");
21852            self.generate_expression(&cast.this)?;
21853            self.write_space();
21854            self.write_keyword("AS");
21855            self.write_space();
21856            self.generate_data_type(&cast.to)?;
21857            self.write(")");
21858            return Ok(());
21859        }
21860
21861        // Dialects without TRY_CAST: generate as regular CAST
21862        let keyword = if matches!(
21863            self.config.dialect,
21864            Some(DialectType::Hive)
21865                | Some(DialectType::MySQL)
21866                | Some(DialectType::SQLite)
21867                | Some(DialectType::Oracle)
21868                | Some(DialectType::ClickHouse)
21869                | Some(DialectType::Redshift)
21870                | Some(DialectType::PostgreSQL)
21871                | Some(DialectType::StarRocks)
21872                | Some(DialectType::Doris)
21873        ) {
21874            "CAST"
21875        } else {
21876            "TRY_CAST"
21877        };
21878
21879        self.write_keyword(keyword);
21880        self.write("(");
21881        self.generate_expression(&cast.this)?;
21882        self.write_space();
21883        self.write_keyword("AS");
21884        self.write_space();
21885        self.generate_data_type(&cast.to)?;
21886
21887        // Output FORMAT clause if present
21888        if let Some(format) = &cast.format {
21889            self.write_space();
21890            self.write_keyword("FORMAT");
21891            self.write_space();
21892            self.generate_expression(format)?;
21893        }
21894
21895        self.write(")");
21896        Ok(())
21897    }
21898
21899    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21900        self.write_keyword("SAFE_CAST");
21901        self.write("(");
21902        self.generate_expression(&cast.this)?;
21903        self.write_space();
21904        self.write_keyword("AS");
21905        self.write_space();
21906        self.generate_data_type(&cast.to)?;
21907
21908        // Output FORMAT clause if present
21909        if let Some(format) = &cast.format {
21910            self.write_space();
21911            self.write_keyword("FORMAT");
21912            self.write_space();
21913            self.generate_expression(format)?;
21914        }
21915
21916        self.write(")");
21917        Ok(())
21918    }
21919
21920    // Array/struct/map access generators
21921
21922    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21923        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21924        // which has lower precedence than bracket subscript ([]).
21925        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21926        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21927        if needs_parens {
21928            self.write("(");
21929        }
21930        self.generate_expression(&s.this)?;
21931        if needs_parens {
21932            self.write(")");
21933        }
21934        self.write("[");
21935        self.generate_expression(&s.index)?;
21936        self.write("]");
21937        Ok(())
21938    }
21939
21940    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21941        self.generate_expression(&d.this)?;
21942        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21943        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21944        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21945            && matches!(
21946                &d.this,
21947                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21948            );
21949        if use_colon {
21950            self.write(":");
21951        } else {
21952            self.write(".");
21953        }
21954        self.generate_identifier(&d.field)
21955    }
21956
21957    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21958        self.generate_expression(&m.this)?;
21959        self.write(".");
21960        // Method names after a dot should not be quoted based on reserved keywords
21961        // Only quote if explicitly marked as quoted in the AST
21962        if m.method.quoted {
21963            let q = self.config.identifier_quote;
21964            self.write(&format!("{}{}{}", q, m.method.name, q));
21965        } else {
21966            self.write(&m.method.name);
21967        }
21968        self.write("(");
21969        for (i, arg) in m.args.iter().enumerate() {
21970            if i > 0 {
21971                self.write(", ");
21972            }
21973            self.generate_expression(arg)?;
21974        }
21975        self.write(")");
21976        Ok(())
21977    }
21978
21979    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21980        // Check if we need to wrap the inner expression in parentheses
21981        // JSON arrow expressions have lower precedence than array subscript
21982        let needs_parens = matches!(
21983            &s.this,
21984            Expression::JsonExtract(f) if f.arrow_syntax
21985        ) || matches!(
21986            &s.this,
21987            Expression::JsonExtractScalar(f) if f.arrow_syntax
21988        );
21989
21990        if needs_parens {
21991            self.write("(");
21992        }
21993        self.generate_expression(&s.this)?;
21994        if needs_parens {
21995            self.write(")");
21996        }
21997        self.write("[");
21998        if let Some(start) = &s.start {
21999            self.generate_expression(start)?;
22000        }
22001        self.write(":");
22002        if let Some(end) = &s.end {
22003            self.generate_expression(end)?;
22004        }
22005        self.write("]");
22006        Ok(())
22007    }
22008
22009    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22010        // Generate left expression, but skip trailing comments if they're already in left_comments
22011        // to avoid duplication (comments are captured as both expr.trailing_comments
22012        // and BinaryOp.left_comments during parsing)
22013        match &op.left {
22014            Expression::Column(col) => {
22015                // Generate column with trailing comments but skip them if they're
22016                // already captured in BinaryOp.left_comments to avoid duplication
22017                if let Some(table) = &col.table {
22018                    self.generate_identifier(table)?;
22019                    self.write(".");
22020                }
22021                self.generate_identifier(&col.name)?;
22022                // Oracle-style join marker (+)
22023                if col.join_mark && self.config.supports_column_join_marks {
22024                    self.write(" (+)");
22025                }
22026                // Output column trailing comments if they're not already in left_comments
22027                if op.left_comments.is_empty() {
22028                    for comment in &col.trailing_comments {
22029                        self.write_space();
22030                        self.write_formatted_comment(comment);
22031                    }
22032                }
22033            }
22034            Expression::Add(inner_op)
22035            | Expression::Sub(inner_op)
22036            | Expression::Mul(inner_op)
22037            | Expression::Div(inner_op)
22038            | Expression::Concat(inner_op) => {
22039                // Generate binary op without its trailing comments
22040                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22041                    Expression::Add(_) => "+",
22042                    Expression::Sub(_) => "-",
22043                    Expression::Mul(_) => "*",
22044                    Expression::Div(_) => "/",
22045                    Expression::Concat(_) => "||",
22046                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22047                })?;
22048            }
22049            _ => {
22050                self.generate_expression(&op.left)?;
22051            }
22052        }
22053        // Output comments after left operand
22054        for comment in &op.left_comments {
22055            self.write_space();
22056            self.write_formatted_comment(comment);
22057        }
22058        if self.config.pretty
22059            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22060            && (operator == "AND" || operator == "OR")
22061        {
22062            self.write_newline();
22063            self.write_indent();
22064            self.write_keyword(operator);
22065        } else {
22066            self.write_space();
22067            if operator.chars().all(|c| c.is_alphabetic()) {
22068                self.write_keyword(operator);
22069            } else {
22070                self.write(operator);
22071            }
22072        }
22073        // Output comments after operator (before right operand)
22074        for comment in &op.operator_comments {
22075            self.write_space();
22076            self.write_formatted_comment(comment);
22077        }
22078        self.write_space();
22079        self.generate_expression(&op.right)?;
22080        // Output trailing comments after right operand
22081        for comment in &op.trailing_comments {
22082            self.write_space();
22083            self.write_formatted_comment(comment);
22084        }
22085        Ok(())
22086    }
22087
22088    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22089        let keyword = connector.keyword();
22090        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22091            return self.generate_binary_op(op, keyword);
22092        };
22093
22094        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22095            let should_wrap = matches!(connector, ConnectorOperator::Or)
22096                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22097                && matches!(
22098                    generator.config.source_dialect,
22099                    Some(DialectType::ClickHouse)
22100                )
22101                && matches!(term, Expression::And(_));
22102            if should_wrap {
22103                generator.write("(");
22104                generator.generate_expression(term)?;
22105                generator.write(")");
22106            } else {
22107                generator.generate_expression(term)?;
22108            }
22109            Ok(())
22110        };
22111
22112        wrap_clickhouse_or_term(self, terms[0])?;
22113        for term in terms.iter().skip(1) {
22114            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22115                self.write_newline();
22116                self.write_indent();
22117                self.write_keyword(keyword);
22118            } else {
22119                self.write_space();
22120                self.write_keyword(keyword);
22121            }
22122            self.write_space();
22123            wrap_clickhouse_or_term(self, term)?;
22124        }
22125
22126        Ok(())
22127    }
22128
22129    fn flatten_connector_terms<'a>(
22130        &self,
22131        root: &'a BinaryOp,
22132        connector: ConnectorOperator,
22133    ) -> Option<Vec<&'a Expression>> {
22134        if !root.left_comments.is_empty()
22135            || !root.operator_comments.is_empty()
22136            || !root.trailing_comments.is_empty()
22137        {
22138            return None;
22139        }
22140
22141        let mut terms = Vec::new();
22142        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22143
22144        while let Some(expr) = stack.pop() {
22145            match (connector, expr) {
22146                (ConnectorOperator::And, Expression::And(inner))
22147                    if inner.left_comments.is_empty()
22148                        && inner.operator_comments.is_empty()
22149                        && inner.trailing_comments.is_empty() =>
22150                {
22151                    stack.push(&inner.right);
22152                    stack.push(&inner.left);
22153                }
22154                (ConnectorOperator::Or, Expression::Or(inner))
22155                    if inner.left_comments.is_empty()
22156                        && inner.operator_comments.is_empty()
22157                        && inner.trailing_comments.is_empty() =>
22158                {
22159                    stack.push(&inner.right);
22160                    stack.push(&inner.left);
22161                }
22162                _ => terms.push(expr),
22163            }
22164        }
22165
22166        if terms.len() > 1 {
22167            Some(terms)
22168        } else {
22169            None
22170        }
22171    }
22172
22173    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22174    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22175        self.generate_expression(&op.left)?;
22176        self.write_space();
22177        // Drill backtick-quotes ILIKE
22178        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22179            self.write("`ILIKE`");
22180        } else {
22181            self.write_keyword(operator);
22182        }
22183        if let Some(quantifier) = &op.quantifier {
22184            self.write_space();
22185            self.write_keyword(quantifier);
22186            // Match Python sqlglot behavior:
22187            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22188            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22189            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22190            let is_any =
22191                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22192            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22193                self.write_space();
22194            }
22195        } else {
22196            self.write_space();
22197        }
22198        self.generate_expression(&op.right)?;
22199        if let Some(escape) = &op.escape {
22200            self.write_space();
22201            self.write_keyword("ESCAPE");
22202            self.write_space();
22203            self.generate_expression(escape)?;
22204        }
22205        Ok(())
22206    }
22207
22208    /// Generate null-safe equality
22209    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22210    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22211        use crate::dialects::DialectType;
22212        self.generate_expression(&op.left)?;
22213        self.write_space();
22214        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22215            self.write("<=>");
22216        } else {
22217            self.write_keyword("IS NOT DISTINCT FROM");
22218        }
22219        self.write_space();
22220        self.generate_expression(&op.right)?;
22221        Ok(())
22222    }
22223
22224    /// Generate IS DISTINCT FROM (null-safe inequality)
22225    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22226        self.generate_expression(&op.left)?;
22227        self.write_space();
22228        self.write_keyword("IS DISTINCT FROM");
22229        self.write_space();
22230        self.generate_expression(&op.right)?;
22231        Ok(())
22232    }
22233
22234    /// Generate binary op without trailing comments (used when nested inside another binary op)
22235    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22236        // Generate left expression, but skip trailing comments
22237        match &op.left {
22238            Expression::Column(col) => {
22239                if let Some(table) = &col.table {
22240                    self.generate_identifier(table)?;
22241                    self.write(".");
22242                }
22243                self.generate_identifier(&col.name)?;
22244                // Oracle-style join marker (+)
22245                if col.join_mark && self.config.supports_column_join_marks {
22246                    self.write(" (+)");
22247                }
22248            }
22249            Expression::Add(inner_op)
22250            | Expression::Sub(inner_op)
22251            | Expression::Mul(inner_op)
22252            | Expression::Div(inner_op)
22253            | Expression::Concat(inner_op) => {
22254                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22255                    Expression::Add(_) => "+",
22256                    Expression::Sub(_) => "-",
22257                    Expression::Mul(_) => "*",
22258                    Expression::Div(_) => "/",
22259                    Expression::Concat(_) => "||",
22260                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22261                })?;
22262            }
22263            _ => {
22264                self.generate_expression(&op.left)?;
22265            }
22266        }
22267        // Output left_comments
22268        for comment in &op.left_comments {
22269            self.write_space();
22270            self.write_formatted_comment(comment);
22271        }
22272        self.write_space();
22273        if operator.chars().all(|c| c.is_alphabetic()) {
22274            self.write_keyword(operator);
22275        } else {
22276            self.write(operator);
22277        }
22278        // Output operator_comments
22279        for comment in &op.operator_comments {
22280            self.write_space();
22281            self.write_formatted_comment(comment);
22282        }
22283        self.write_space();
22284        // Generate right expression, but skip trailing comments if it's a Column
22285        // (the parent's left_comments will output them)
22286        match &op.right {
22287            Expression::Column(col) => {
22288                if let Some(table) = &col.table {
22289                    self.generate_identifier(table)?;
22290                    self.write(".");
22291                }
22292                self.generate_identifier(&col.name)?;
22293                // Oracle-style join marker (+)
22294                if col.join_mark && self.config.supports_column_join_marks {
22295                    self.write(" (+)");
22296                }
22297            }
22298            _ => {
22299                self.generate_expression(&op.right)?;
22300            }
22301        }
22302        // Skip trailing_comments - parent will handle them via its left_comments
22303        Ok(())
22304    }
22305
22306    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22307        if operator.chars().all(|c| c.is_alphabetic()) {
22308            self.write_keyword(operator);
22309            self.write_space();
22310        } else {
22311            self.write(operator);
22312            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22313            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22314                self.write_space();
22315            }
22316        }
22317        self.generate_expression(&op.this)
22318    }
22319
22320    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22321        // Generic mode supports two styles for negated IN:
22322        // - Prefix: NOT a IN (...)
22323        // - Infix:  a NOT IN (...)
22324        let is_generic =
22325            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22326        let use_prefix_not =
22327            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22328        if use_prefix_not {
22329            self.write_keyword("NOT");
22330            self.write_space();
22331        }
22332        self.generate_expression(&in_expr.this)?;
22333        if in_expr.global {
22334            self.write_space();
22335            self.write_keyword("GLOBAL");
22336        }
22337        if in_expr.not && !use_prefix_not {
22338            self.write_space();
22339            self.write_keyword("NOT");
22340        }
22341        self.write_space();
22342        self.write_keyword("IN");
22343
22344        // BigQuery: IN UNNEST(expr)
22345        if let Some(unnest_expr) = &in_expr.unnest {
22346            self.write_space();
22347            self.write_keyword("UNNEST");
22348            self.write("(");
22349            self.generate_expression(unnest_expr)?;
22350            self.write(")");
22351            return Ok(());
22352        }
22353
22354        if let Some(query) = &in_expr.query {
22355            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22356            // vs a subquery (col IN (SELECT ...))
22357            let is_bare = in_expr.expressions.is_empty()
22358                && !matches!(
22359                    query,
22360                    Expression::Select(_)
22361                        | Expression::Union(_)
22362                        | Expression::Intersect(_)
22363                        | Expression::Except(_)
22364                        | Expression::Subquery(_)
22365                );
22366            if is_bare {
22367                // Bare identifier: no parentheses
22368                self.write_space();
22369                self.generate_expression(query)?;
22370            } else {
22371                // Subquery: with parentheses
22372                self.write(" (");
22373                let is_statement = matches!(
22374                    query,
22375                    Expression::Select(_)
22376                        | Expression::Union(_)
22377                        | Expression::Intersect(_)
22378                        | Expression::Except(_)
22379                        | Expression::Subquery(_)
22380                );
22381                if self.config.pretty && is_statement {
22382                    self.write_newline();
22383                    self.indent_level += 1;
22384                    self.write_indent();
22385                }
22386                self.generate_expression(query)?;
22387                if self.config.pretty && is_statement {
22388                    self.write_newline();
22389                    self.indent_level -= 1;
22390                    self.write_indent();
22391                }
22392                self.write(")");
22393            }
22394        } else {
22395            // DuckDB: IN without parentheses for single expression that is NOT a literal
22396            // (array/list membership like 'red' IN tbl.flags)
22397            // ClickHouse: IN without parentheses for single non-array expressions
22398            let is_duckdb = matches!(
22399                self.config.dialect,
22400                Some(crate::dialects::DialectType::DuckDB)
22401            );
22402            let is_clickhouse = matches!(
22403                self.config.dialect,
22404                Some(crate::dialects::DialectType::ClickHouse)
22405            );
22406            let single_expr = in_expr.expressions.len() == 1;
22407            if is_clickhouse && single_expr {
22408                if let Expression::Array(arr) = &in_expr.expressions[0] {
22409                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22410                    self.write(" (");
22411                    for (i, expr) in arr.expressions.iter().enumerate() {
22412                        if i > 0 {
22413                            self.write(", ");
22414                        }
22415                        self.generate_expression(expr)?;
22416                    }
22417                    self.write(")");
22418                } else if in_expr.is_field {
22419                    self.write_space();
22420                    self.generate_expression(&in_expr.expressions[0])?;
22421                } else {
22422                    self.write(" (");
22423                    self.generate_expression(&in_expr.expressions[0])?;
22424                    self.write(")");
22425                }
22426            } else {
22427                let is_bare_ref = single_expr
22428                    && matches!(
22429                        &in_expr.expressions[0],
22430                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22431                    );
22432                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22433                    // Bare field reference (no parens in source): IN identifier
22434                    // Also DuckDB: IN without parentheses for array/list membership
22435                    self.write_space();
22436                    self.generate_expression(&in_expr.expressions[0])?;
22437                } else {
22438                    // Standard IN (list)
22439                    self.write(" (");
22440                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22441                        if i > 0 {
22442                            self.write(", ");
22443                        }
22444                        self.generate_expression(expr)?;
22445                    }
22446                    self.write(")");
22447                }
22448            }
22449        }
22450
22451        Ok(())
22452    }
22453
22454    fn generate_between(&mut self, between: &Between) -> Result<()> {
22455        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22456        let use_prefix_not = between.not
22457            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22458        if use_prefix_not {
22459            self.write_keyword("NOT");
22460            self.write_space();
22461        }
22462        self.generate_expression(&between.this)?;
22463        if between.not && !use_prefix_not {
22464            self.write_space();
22465            self.write_keyword("NOT");
22466        }
22467        self.write_space();
22468        self.write_keyword("BETWEEN");
22469        // Emit SYMMETRIC/ASYMMETRIC if present
22470        if let Some(sym) = between.symmetric {
22471            if sym {
22472                self.write(" SYMMETRIC");
22473            } else {
22474                self.write(" ASYMMETRIC");
22475            }
22476        }
22477        self.write_space();
22478        self.generate_expression(&between.low)?;
22479        self.write_space();
22480        self.write_keyword("AND");
22481        self.write_space();
22482        self.generate_expression(&between.high)
22483    }
22484
22485    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22486        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22487        let use_prefix_not = is_null.not
22488            && (self.config.dialect.is_none()
22489                || self.config.dialect == Some(DialectType::Generic)
22490                || is_null.postfix_form);
22491        if use_prefix_not {
22492            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22493            self.write_keyword("NOT");
22494            self.write_space();
22495            self.generate_expression(&is_null.this)?;
22496            self.write_space();
22497            self.write_keyword("IS");
22498            self.write_space();
22499            self.write_keyword("NULL");
22500        } else {
22501            self.generate_expression(&is_null.this)?;
22502            self.write_space();
22503            self.write_keyword("IS");
22504            if is_null.not {
22505                self.write_space();
22506                self.write_keyword("NOT");
22507            }
22508            self.write_space();
22509            self.write_keyword("NULL");
22510        }
22511        Ok(())
22512    }
22513
22514    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22515        self.generate_expression(&is_true.this)?;
22516        self.write_space();
22517        self.write_keyword("IS");
22518        if is_true.not {
22519            self.write_space();
22520            self.write_keyword("NOT");
22521        }
22522        self.write_space();
22523        self.write_keyword("TRUE");
22524        Ok(())
22525    }
22526
22527    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22528        self.generate_expression(&is_false.this)?;
22529        self.write_space();
22530        self.write_keyword("IS");
22531        if is_false.not {
22532            self.write_space();
22533            self.write_keyword("NOT");
22534        }
22535        self.write_space();
22536        self.write_keyword("FALSE");
22537        Ok(())
22538    }
22539
22540    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22541        self.generate_expression(&is_json.this)?;
22542        self.write_space();
22543        self.write_keyword("IS");
22544        if is_json.negated {
22545            self.write_space();
22546            self.write_keyword("NOT");
22547        }
22548        self.write_space();
22549        self.write_keyword("JSON");
22550
22551        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22552        if let Some(ref json_type) = is_json.json_type {
22553            self.write_space();
22554            self.write_keyword(json_type);
22555        }
22556
22557        // Output key uniqueness constraint if specified
22558        match &is_json.unique_keys {
22559            Some(JsonUniqueKeys::With) => {
22560                self.write_space();
22561                self.write_keyword("WITH UNIQUE KEYS");
22562            }
22563            Some(JsonUniqueKeys::Without) => {
22564                self.write_space();
22565                self.write_keyword("WITHOUT UNIQUE KEYS");
22566            }
22567            Some(JsonUniqueKeys::Shorthand) => {
22568                self.write_space();
22569                self.write_keyword("UNIQUE KEYS");
22570            }
22571            None => {}
22572        }
22573
22574        Ok(())
22575    }
22576
22577    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22578        self.generate_expression(&is_expr.left)?;
22579        self.write_space();
22580        self.write_keyword("IS");
22581        self.write_space();
22582        self.generate_expression(&is_expr.right)
22583    }
22584
22585    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22586        if exists.not {
22587            self.write_keyword("NOT");
22588            self.write_space();
22589        }
22590        self.write_keyword("EXISTS");
22591        self.write("(");
22592        let is_statement = matches!(
22593            &exists.this,
22594            Expression::Select(_)
22595                | Expression::Union(_)
22596                | Expression::Intersect(_)
22597                | Expression::Except(_)
22598        );
22599        if self.config.pretty && is_statement {
22600            self.write_newline();
22601            self.indent_level += 1;
22602            self.write_indent();
22603            self.generate_expression(&exists.this)?;
22604            self.write_newline();
22605            self.indent_level -= 1;
22606            self.write_indent();
22607            self.write(")");
22608        } else {
22609            self.generate_expression(&exists.this)?;
22610            self.write(")");
22611        }
22612        Ok(())
22613    }
22614
22615    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22616        self.generate_expression(&op.left)?;
22617        self.write_space();
22618        self.write_keyword("MEMBER OF");
22619        self.write("(");
22620        self.generate_expression(&op.right)?;
22621        self.write(")");
22622        Ok(())
22623    }
22624
22625    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22626        if subquery.lateral {
22627            self.write_keyword("LATERAL");
22628            self.write_space();
22629        }
22630
22631        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22632        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22633        // to carry the LIMIT modifier without adding more parens
22634        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22635            matches!(
22636                &p.this,
22637                Expression::Select(_)
22638                    | Expression::Union(_)
22639                    | Expression::Intersect(_)
22640                    | Expression::Except(_)
22641                    | Expression::Subquery(_)
22642            )
22643        } else {
22644            false
22645        };
22646
22647        // Check if inner expression is a statement for pretty formatting
22648        let is_statement = matches!(
22649            &subquery.this,
22650            Expression::Select(_)
22651                | Expression::Union(_)
22652                | Expression::Intersect(_)
22653                | Expression::Except(_)
22654                | Expression::Merge(_)
22655        );
22656
22657        if !skip_outer_parens {
22658            self.write("(");
22659            if self.config.pretty && is_statement {
22660                self.write_newline();
22661                self.indent_level += 1;
22662                self.write_indent();
22663            }
22664        }
22665        self.generate_expression(&subquery.this)?;
22666
22667        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22668        if subquery.modifiers_inside {
22669            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22670            if let Some(order_by) = &subquery.order_by {
22671                self.write_space();
22672                self.write_keyword("ORDER BY");
22673                self.write_space();
22674                for (i, ord) in order_by.expressions.iter().enumerate() {
22675                    if i > 0 {
22676                        self.write(", ");
22677                    }
22678                    self.generate_ordered(ord)?;
22679                }
22680            }
22681
22682            if let Some(limit) = &subquery.limit {
22683                self.write_space();
22684                self.write_keyword("LIMIT");
22685                self.write_space();
22686                self.generate_expression(&limit.this)?;
22687                if limit.percent {
22688                    self.write_space();
22689                    self.write_keyword("PERCENT");
22690                }
22691            }
22692
22693            if let Some(offset) = &subquery.offset {
22694                self.write_space();
22695                self.write_keyword("OFFSET");
22696                self.write_space();
22697                self.generate_expression(&offset.this)?;
22698            }
22699        }
22700
22701        if !skip_outer_parens {
22702            if self.config.pretty && is_statement {
22703                self.write_newline();
22704                self.indent_level -= 1;
22705                self.write_indent();
22706            }
22707            self.write(")");
22708        }
22709
22710        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22711        if !subquery.modifiers_inside {
22712            if let Some(order_by) = &subquery.order_by {
22713                self.write_space();
22714                self.write_keyword("ORDER BY");
22715                self.write_space();
22716                for (i, ord) in order_by.expressions.iter().enumerate() {
22717                    if i > 0 {
22718                        self.write(", ");
22719                    }
22720                    self.generate_ordered(ord)?;
22721                }
22722            }
22723
22724            if let Some(limit) = &subquery.limit {
22725                self.write_space();
22726                self.write_keyword("LIMIT");
22727                self.write_space();
22728                self.generate_expression(&limit.this)?;
22729                if limit.percent {
22730                    self.write_space();
22731                    self.write_keyword("PERCENT");
22732                }
22733            }
22734
22735            if let Some(offset) = &subquery.offset {
22736                self.write_space();
22737                self.write_keyword("OFFSET");
22738                self.write_space();
22739                self.generate_expression(&offset.this)?;
22740            }
22741
22742            // Generate DISTRIBUTE BY (Hive/Spark)
22743            if let Some(distribute_by) = &subquery.distribute_by {
22744                self.write_space();
22745                self.write_keyword("DISTRIBUTE BY");
22746                self.write_space();
22747                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22748                    if i > 0 {
22749                        self.write(", ");
22750                    }
22751                    self.generate_expression(expr)?;
22752                }
22753            }
22754
22755            // Generate SORT BY (Hive/Spark)
22756            if let Some(sort_by) = &subquery.sort_by {
22757                self.write_space();
22758                self.write_keyword("SORT BY");
22759                self.write_space();
22760                for (i, ord) in sort_by.expressions.iter().enumerate() {
22761                    if i > 0 {
22762                        self.write(", ");
22763                    }
22764                    self.generate_ordered(ord)?;
22765                }
22766            }
22767
22768            // Generate CLUSTER BY (Hive/Spark)
22769            if let Some(cluster_by) = &subquery.cluster_by {
22770                self.write_space();
22771                self.write_keyword("CLUSTER BY");
22772                self.write_space();
22773                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22774                    if i > 0 {
22775                        self.write(", ");
22776                    }
22777                    self.generate_ordered(ord)?;
22778                }
22779            }
22780        }
22781
22782        if let Some(alias) = &subquery.alias {
22783            self.write_space();
22784            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
22785                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
22786                    && !subquery.alias_explicit_as);
22787            if !skip_as {
22788                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22789                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
22790                } else {
22791                    self.write_keyword("AS");
22792                }
22793                self.write_space();
22794            }
22795            self.generate_identifier(alias)?;
22796            if !subquery.column_aliases.is_empty() {
22797                self.write("(");
22798                for (i, col) in subquery.column_aliases.iter().enumerate() {
22799                    if i > 0 {
22800                        self.write(", ");
22801                    }
22802                    self.generate_identifier(col)?;
22803                }
22804                self.write(")");
22805            }
22806        }
22807        // Output trailing comments
22808        for comment in &subquery.trailing_comments {
22809            self.write(" ");
22810            self.write_formatted_comment(comment);
22811        }
22812        Ok(())
22813    }
22814
22815    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22816        // Generate WITH clause if present
22817        if let Some(ref with) = pivot.with {
22818            self.generate_with(with)?;
22819            self.write_space();
22820        }
22821
22822        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22823
22824        // Check for Redshift UNPIVOT in FROM clause:
22825        // UNPIVOT expr [AS val AT attr]
22826        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22827        let is_redshift_unpivot = pivot.unpivot
22828            && pivot.expressions.is_empty()
22829            && pivot.fields.is_empty()
22830            && pivot.using.is_empty()
22831            && pivot.into.is_none()
22832            && !matches!(&pivot.this, Expression::Null(_));
22833
22834        if is_redshift_unpivot {
22835            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22836            self.write_keyword("UNPIVOT");
22837            self.write_space();
22838            self.generate_expression(&pivot.this)?;
22839            // Alias - for Redshift it can be "val AT attr" format
22840            if let Some(alias) = &pivot.alias {
22841                self.write_space();
22842                self.write_keyword("AS");
22843                self.write_space();
22844                // The alias might contain " AT " for the attr part
22845                self.write(&alias.name);
22846            }
22847            return Ok(());
22848        }
22849
22850        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22851        let is_simplified = !pivot.using.is_empty()
22852            || pivot.into.is_some()
22853            || (pivot.fields.is_empty()
22854                && !pivot.expressions.is_empty()
22855                && !matches!(&pivot.this, Expression::Null(_)));
22856
22857        if is_simplified {
22858            // DuckDB simplified syntax:
22859            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22860            //   UNPIVOT table ON cols INTO NAME col VALUE col
22861            self.write_keyword(direction);
22862            self.write_space();
22863            self.generate_expression(&pivot.this)?;
22864
22865            if !pivot.expressions.is_empty() {
22866                self.write_space();
22867                self.write_keyword("ON");
22868                self.write_space();
22869                for (i, expr) in pivot.expressions.iter().enumerate() {
22870                    if i > 0 {
22871                        self.write(", ");
22872                    }
22873                    self.generate_expression(expr)?;
22874                }
22875            }
22876
22877            // INTO (for UNPIVOT)
22878            if let Some(into) = &pivot.into {
22879                self.write_space();
22880                self.write_keyword("INTO");
22881                self.write_space();
22882                self.generate_expression(into)?;
22883            }
22884
22885            // USING (for PIVOT)
22886            if !pivot.using.is_empty() {
22887                self.write_space();
22888                self.write_keyword("USING");
22889                self.write_space();
22890                for (i, expr) in pivot.using.iter().enumerate() {
22891                    if i > 0 {
22892                        self.write(", ");
22893                    }
22894                    self.generate_expression(expr)?;
22895                }
22896            }
22897
22898            // GROUP BY
22899            if let Some(group) = &pivot.group {
22900                self.write_space();
22901                self.generate_expression(group)?;
22902            }
22903        } else {
22904            // Standard syntax:
22905            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22906            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22907            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22908            if !matches!(&pivot.this, Expression::Null(_)) {
22909                self.generate_expression(&pivot.this)?;
22910                self.write_space();
22911            }
22912            self.write_keyword(direction);
22913            self.write("(");
22914
22915            // Aggregation expressions
22916            for (i, expr) in pivot.expressions.iter().enumerate() {
22917                if i > 0 {
22918                    self.write(", ");
22919                }
22920                self.generate_expression(expr)?;
22921            }
22922
22923            // FOR...IN fields
22924            if !pivot.fields.is_empty() {
22925                if !pivot.expressions.is_empty() {
22926                    self.write_space();
22927                }
22928                self.write_keyword("FOR");
22929                self.write_space();
22930                for (i, field) in pivot.fields.iter().enumerate() {
22931                    if i > 0 {
22932                        self.write_space();
22933                    }
22934                    // field is an In expression: column IN (values)
22935                    self.generate_expression(field)?;
22936                }
22937            }
22938
22939            // DEFAULT ON NULL
22940            if let Some(default_val) = &pivot.default_on_null {
22941                self.write_space();
22942                self.write_keyword("DEFAULT ON NULL");
22943                self.write(" (");
22944                self.generate_expression(default_val)?;
22945                self.write(")");
22946            }
22947
22948            // GROUP BY inside PIVOT parens
22949            if let Some(group) = &pivot.group {
22950                self.write_space();
22951                self.generate_expression(group)?;
22952            }
22953
22954            self.write(")");
22955        }
22956
22957        // Alias
22958        if let Some(alias) = &pivot.alias {
22959            self.write_space();
22960            self.write_keyword("AS");
22961            self.write_space();
22962            self.generate_identifier(alias)?;
22963        }
22964
22965        Ok(())
22966    }
22967
22968    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22969        self.generate_expression(&unpivot.this)?;
22970        self.write_space();
22971        self.write_keyword("UNPIVOT");
22972        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22973        if let Some(include) = unpivot.include_nulls {
22974            self.write_space();
22975            if include {
22976                self.write_keyword("INCLUDE NULLS");
22977            } else {
22978                self.write_keyword("EXCLUDE NULLS");
22979            }
22980            self.write_space();
22981        }
22982        self.write("(");
22983        if unpivot.value_column_parenthesized {
22984            self.write("(");
22985        }
22986        self.generate_identifier(&unpivot.value_column)?;
22987        // Output additional value columns if present
22988        for extra_col in &unpivot.extra_value_columns {
22989            self.write(", ");
22990            self.generate_identifier(extra_col)?;
22991        }
22992        if unpivot.value_column_parenthesized {
22993            self.write(")");
22994        }
22995        self.write_space();
22996        self.write_keyword("FOR");
22997        self.write_space();
22998        self.generate_identifier(&unpivot.name_column)?;
22999        self.write_space();
23000        self.write_keyword("IN");
23001        self.write(" (");
23002        for (i, col) in unpivot.columns.iter().enumerate() {
23003            if i > 0 {
23004                self.write(", ");
23005            }
23006            self.generate_expression(col)?;
23007        }
23008        self.write("))");
23009        if let Some(alias) = &unpivot.alias {
23010            self.write_space();
23011            self.write_keyword("AS");
23012            self.write_space();
23013            self.generate_identifier(alias)?;
23014        }
23015        Ok(())
23016    }
23017
23018    fn generate_values(&mut self, values: &Values) -> Result<()> {
23019        self.write_keyword("VALUES");
23020        for (i, row) in values.expressions.iter().enumerate() {
23021            if i > 0 {
23022                self.write(",");
23023            }
23024            self.write(" (");
23025            for (j, expr) in row.expressions.iter().enumerate() {
23026                if j > 0 {
23027                    self.write(", ");
23028                }
23029                self.generate_expression(expr)?;
23030            }
23031            self.write(")");
23032        }
23033        if let Some(alias) = &values.alias {
23034            self.write_space();
23035            self.write_keyword("AS");
23036            self.write_space();
23037            self.generate_identifier(alias)?;
23038            if !values.column_aliases.is_empty() {
23039                self.write("(");
23040                for (i, col) in values.column_aliases.iter().enumerate() {
23041                    if i > 0 {
23042                        self.write(", ");
23043                    }
23044                    self.generate_identifier(col)?;
23045                }
23046                self.write(")");
23047            }
23048        }
23049        Ok(())
23050    }
23051
23052    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23053        // Apply struct name inheritance for target dialects that need it
23054        let needs_inheritance = matches!(
23055            self.config.dialect,
23056            Some(DialectType::DuckDB)
23057                | Some(DialectType::Spark)
23058                | Some(DialectType::Databricks)
23059                | Some(DialectType::Hive)
23060                | Some(DialectType::Snowflake)
23061                | Some(DialectType::Presto)
23062                | Some(DialectType::Trino)
23063        );
23064        let propagated: Vec<Expression>;
23065        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23066            propagated = Self::inherit_struct_field_names(&arr.expressions);
23067            &propagated
23068        } else {
23069            &arr.expressions
23070        };
23071
23072        // Generic mode: ARRAY(1, 2, 3) with parentheses
23073        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23074        let use_parens =
23075            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23076        if !self.config.array_bracket_only {
23077            self.write_keyword("ARRAY");
23078        }
23079        if use_parens {
23080            self.write("(");
23081        } else {
23082            self.write("[");
23083        }
23084        for (i, expr) in expressions.iter().enumerate() {
23085            if i > 0 {
23086                self.write(", ");
23087            }
23088            self.generate_expression(expr)?;
23089        }
23090        if use_parens {
23091            self.write(")");
23092        } else {
23093            self.write("]");
23094        }
23095        Ok(())
23096    }
23097
23098    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23099        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23100        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23101        if tuple.expressions.len() == 2 {
23102            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23103                // First element is the function/expression, second is the TableAlias
23104                self.generate_expression(&tuple.expressions[0])?;
23105                self.write_space();
23106                self.write_keyword("AS");
23107                self.write_space();
23108                self.generate_expression(&tuple.expressions[1])?;
23109                return Ok(());
23110            }
23111        }
23112
23113        // In pretty mode, format long tuples with each element on a new line
23114        // Only expand if total width exceeds threshold
23115        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23116            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23117            for expr in &tuple.expressions {
23118                expr_strings.push(self.generate_to_string(expr)?);
23119            }
23120            self.too_wide(&expr_strings)
23121        } else {
23122            false
23123        };
23124
23125        if expand_tuple {
23126            self.write("(");
23127            self.write_newline();
23128            self.indent_level += 1;
23129            for (i, expr) in tuple.expressions.iter().enumerate() {
23130                if i > 0 {
23131                    self.write(",");
23132                    self.write_newline();
23133                }
23134                self.write_indent();
23135                self.generate_expression(expr)?;
23136            }
23137            self.indent_level -= 1;
23138            self.write_newline();
23139            self.write_indent();
23140            self.write(")");
23141        } else {
23142            self.write("(");
23143            for (i, expr) in tuple.expressions.iter().enumerate() {
23144                if i > 0 {
23145                    self.write(", ");
23146                }
23147                self.generate_expression(expr)?;
23148            }
23149            self.write(")");
23150        }
23151        Ok(())
23152    }
23153
23154    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23155        self.generate_expression(&pipe.this)?;
23156        self.write(" |> ");
23157        self.generate_expression(&pipe.expression)?;
23158        Ok(())
23159    }
23160
23161    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23162        self.generate_expression(&ordered.this)?;
23163        if ordered.desc {
23164            self.write_space();
23165            self.write_keyword("DESC");
23166        } else if ordered.explicit_asc {
23167            self.write_space();
23168            self.write_keyword("ASC");
23169        }
23170        if let Some(nulls_first) = ordered.nulls_first {
23171            if self.config.null_ordering_supported
23172                || !matches!(self.config.dialect, Some(DialectType::Fabric))
23173            {
23174                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23175                // for the dialect. Different dialects have different NULL ordering defaults:
23176                //
23177                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23178                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23179                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23180                //
23181                // nulls_are_small (Spark, Hive, BigQuery, most others):
23182                //   - ASC: NULLS FIRST is default
23183                //   - DESC: NULLS LAST is default
23184                //
23185                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23186                //   - NULLS LAST is always the default regardless of sort direction
23187                let is_asc = !ordered.desc;
23188                let is_nulls_are_large = matches!(
23189                    self.config.dialect,
23190                    Some(DialectType::Oracle)
23191                        | Some(DialectType::PostgreSQL)
23192                        | Some(DialectType::Redshift)
23193                        | Some(DialectType::Snowflake)
23194                );
23195                let is_nulls_are_last = matches!(
23196                    self.config.dialect,
23197                    Some(DialectType::Dremio)
23198                        | Some(DialectType::DuckDB)
23199                        | Some(DialectType::Presto)
23200                        | Some(DialectType::Trino)
23201                        | Some(DialectType::Athena)
23202                        | Some(DialectType::ClickHouse)
23203                        | Some(DialectType::Drill)
23204                        | Some(DialectType::Exasol)
23205                );
23206
23207                // Check if the NULLS ordering matches the default for this dialect
23208                let is_default_nulls = if is_nulls_are_large {
23209                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23210                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23211                } else if is_nulls_are_last {
23212                    // For nulls_are_last: NULLS LAST is always default
23213                    !nulls_first
23214                } else {
23215                    false
23216                };
23217
23218                if !is_default_nulls {
23219                    self.write_space();
23220                    self.write_keyword("NULLS");
23221                    self.write_space();
23222                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23223                }
23224            }
23225        }
23226        // WITH FILL clause (ClickHouse)
23227        if let Some(ref with_fill) = ordered.with_fill {
23228            self.write_space();
23229            self.generate_with_fill(with_fill)?;
23230        }
23231        Ok(())
23232    }
23233
23234    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23235    fn write_clickhouse_type(&mut self, type_str: &str) {
23236        if self.clickhouse_nullable_depth < 0 {
23237            // Map key context: don't wrap in Nullable
23238            self.write(type_str);
23239        } else {
23240            self.write(&format!("Nullable({})", type_str));
23241        }
23242    }
23243
23244    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23245        use crate::dialects::DialectType;
23246
23247        match dt {
23248            DataType::Boolean => {
23249                // Dialect-specific boolean type mappings
23250                match self.config.dialect {
23251                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23252                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23253                    Some(DialectType::Oracle) => {
23254                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23255                        self.write_keyword("NUMBER(1)")
23256                    }
23257                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23258                    _ => self.write_keyword("BOOLEAN"),
23259                }
23260            }
23261            DataType::TinyInt { length } => {
23262                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23263                // Dremio maps TINYINT to INT
23264                // ClickHouse maps TINYINT to Int8
23265                match self.config.dialect {
23266                    Some(DialectType::PostgreSQL)
23267                    | Some(DialectType::Redshift)
23268                    | Some(DialectType::Oracle)
23269                    | Some(DialectType::Exasol) => {
23270                        self.write_keyword("SMALLINT");
23271                    }
23272                    Some(DialectType::Teradata) => {
23273                        // Teradata uses BYTEINT for smallest integer
23274                        self.write_keyword("BYTEINT");
23275                    }
23276                    Some(DialectType::Dremio) => {
23277                        // Dremio maps TINYINT to INT
23278                        self.write_keyword("INT");
23279                    }
23280                    Some(DialectType::ClickHouse) => {
23281                        self.write_clickhouse_type("Int8");
23282                    }
23283                    _ => {
23284                        self.write_keyword("TINYINT");
23285                    }
23286                }
23287                if let Some(n) = length {
23288                    if !matches!(
23289                        self.config.dialect,
23290                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23291                    ) {
23292                        self.write(&format!("({})", n));
23293                    }
23294                }
23295            }
23296            DataType::SmallInt { length } => {
23297                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23298                match self.config.dialect {
23299                    Some(DialectType::Dremio) => {
23300                        self.write_keyword("INT");
23301                    }
23302                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23303                        self.write_keyword("INTEGER");
23304                    }
23305                    Some(DialectType::BigQuery) => {
23306                        self.write_keyword("INT64");
23307                    }
23308                    Some(DialectType::ClickHouse) => {
23309                        self.write_clickhouse_type("Int16");
23310                    }
23311                    _ => {
23312                        self.write_keyword("SMALLINT");
23313                        if let Some(n) = length {
23314                            self.write(&format!("({})", n));
23315                        }
23316                    }
23317                }
23318            }
23319            DataType::Int {
23320                length,
23321                integer_spelling: _,
23322            } => {
23323                // BigQuery uses INT64 for INT
23324                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23325                    self.write_keyword("INT64");
23326                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23327                    self.write_clickhouse_type("Int32");
23328                } else {
23329                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23330                    let use_integer = match self.config.dialect {
23331                        Some(DialectType::TSQL)
23332                        | Some(DialectType::Fabric)
23333                        | Some(DialectType::Presto)
23334                        | Some(DialectType::Trino)
23335                        | Some(DialectType::SQLite)
23336                        | Some(DialectType::Redshift) => true,
23337                        _ => false,
23338                    };
23339                    if use_integer {
23340                        self.write_keyword("INTEGER");
23341                    } else {
23342                        self.write_keyword("INT");
23343                    }
23344                    if let Some(n) = length {
23345                        self.write(&format!("({})", n));
23346                    }
23347                }
23348            }
23349            DataType::BigInt { length } => {
23350                // Dialect-specific bigint type mappings
23351                match self.config.dialect {
23352                    Some(DialectType::Oracle) => {
23353                        // Oracle doesn't have BIGINT, uses INT
23354                        self.write_keyword("INT");
23355                    }
23356                    Some(DialectType::ClickHouse) => {
23357                        self.write_clickhouse_type("Int64");
23358                    }
23359                    _ => {
23360                        self.write_keyword("BIGINT");
23361                        if let Some(n) = length {
23362                            self.write(&format!("({})", n));
23363                        }
23364                    }
23365                }
23366            }
23367            DataType::Float {
23368                precision,
23369                scale,
23370                real_spelling,
23371            } => {
23372                // Dialect-specific float type mappings
23373                // If real_spelling is true, preserve REAL; otherwise use dialect default
23374                // Spark/Hive don't support REAL, always use FLOAT
23375                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23376                    self.write_clickhouse_type("Float32");
23377                } else if *real_spelling
23378                    && !matches!(
23379                        self.config.dialect,
23380                        Some(DialectType::Spark)
23381                            | Some(DialectType::Databricks)
23382                            | Some(DialectType::Hive)
23383                            | Some(DialectType::Snowflake)
23384                            | Some(DialectType::MySQL)
23385                            | Some(DialectType::BigQuery)
23386                    )
23387                {
23388                    self.write_keyword("REAL")
23389                } else {
23390                    match self.config.dialect {
23391                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23392                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23393                        _ => self.write_keyword("FLOAT"),
23394                    }
23395                }
23396                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23397                // Spark/Hive don't support FLOAT(precision)
23398                if !matches!(
23399                    self.config.dialect,
23400                    Some(DialectType::Spark)
23401                        | Some(DialectType::Databricks)
23402                        | Some(DialectType::Hive)
23403                        | Some(DialectType::Presto)
23404                        | Some(DialectType::Trino)
23405                ) {
23406                    if let Some(p) = precision {
23407                        self.write(&format!("({}", p));
23408                        if let Some(s) = scale {
23409                            self.write(&format!(", {})", s));
23410                        } else {
23411                            self.write(")");
23412                        }
23413                    }
23414                }
23415            }
23416            DataType::Double { precision, scale } => {
23417                // Dialect-specific double type mappings
23418                match self.config.dialect {
23419                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23420                        self.write_keyword("FLOAT")
23421                    } // SQL Server/Fabric FLOAT is double
23422                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23423                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23424                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23425                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23426                    Some(DialectType::PostgreSQL)
23427                    | Some(DialectType::Redshift)
23428                    | Some(DialectType::Teradata)
23429                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23430                    _ => self.write_keyword("DOUBLE"),
23431                }
23432                // MySQL supports DOUBLE(precision, scale)
23433                if let Some(p) = precision {
23434                    self.write(&format!("({}", p));
23435                    if let Some(s) = scale {
23436                        self.write(&format!(", {})", s));
23437                    } else {
23438                        self.write(")");
23439                    }
23440                }
23441            }
23442            DataType::Decimal { precision, scale } => {
23443                // Dialect-specific decimal type mappings
23444                match self.config.dialect {
23445                    Some(DialectType::ClickHouse) => {
23446                        self.write("Decimal");
23447                        if let Some(p) = precision {
23448                            self.write(&format!("({}", p));
23449                            if let Some(s) = scale {
23450                                self.write(&format!(", {}", s));
23451                            }
23452                            self.write(")");
23453                        }
23454                    }
23455                    Some(DialectType::Oracle) => {
23456                        // Oracle uses NUMBER instead of DECIMAL
23457                        self.write_keyword("NUMBER");
23458                        if let Some(p) = precision {
23459                            self.write(&format!("({}", p));
23460                            if let Some(s) = scale {
23461                                self.write(&format!(", {}", s));
23462                            }
23463                            self.write(")");
23464                        }
23465                    }
23466                    Some(DialectType::BigQuery) => {
23467                        // BigQuery uses NUMERIC instead of DECIMAL
23468                        self.write_keyword("NUMERIC");
23469                        if let Some(p) = precision {
23470                            self.write(&format!("({}", p));
23471                            if let Some(s) = scale {
23472                                self.write(&format!(", {}", s));
23473                            }
23474                            self.write(")");
23475                        }
23476                    }
23477                    _ => {
23478                        self.write_keyword("DECIMAL");
23479                        if let Some(p) = precision {
23480                            self.write(&format!("({}", p));
23481                            if let Some(s) = scale {
23482                                self.write(&format!(", {}", s));
23483                            }
23484                            self.write(")");
23485                        }
23486                    }
23487                }
23488            }
23489            DataType::Char { length } => {
23490                // Dialect-specific char type mappings
23491                match self.config.dialect {
23492                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23493                        // DuckDB/SQLite maps CHAR to TEXT
23494                        self.write_keyword("TEXT");
23495                    }
23496                    Some(DialectType::Hive)
23497                    | Some(DialectType::Spark)
23498                    | Some(DialectType::Databricks) => {
23499                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23500                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23501                        if length.is_some()
23502                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23503                        {
23504                            self.write_keyword("CHAR");
23505                            if let Some(n) = length {
23506                                self.write(&format!("({})", n));
23507                            }
23508                        } else {
23509                            self.write_keyword("STRING");
23510                        }
23511                    }
23512                    Some(DialectType::Dremio) => {
23513                        // Dremio maps CHAR to VARCHAR
23514                        self.write_keyword("VARCHAR");
23515                        if let Some(n) = length {
23516                            self.write(&format!("({})", n));
23517                        }
23518                    }
23519                    _ => {
23520                        self.write_keyword("CHAR");
23521                        if let Some(n) = length {
23522                            self.write(&format!("({})", n));
23523                        }
23524                    }
23525                }
23526            }
23527            DataType::VarChar {
23528                length,
23529                parenthesized_length,
23530            } => {
23531                // Dialect-specific varchar type mappings
23532                match self.config.dialect {
23533                    Some(DialectType::Oracle) => {
23534                        self.write_keyword("VARCHAR2");
23535                        if let Some(n) = length {
23536                            self.write(&format!("({})", n));
23537                        }
23538                    }
23539                    Some(DialectType::DuckDB) => {
23540                        // DuckDB maps VARCHAR to TEXT, preserving length
23541                        self.write_keyword("TEXT");
23542                        if let Some(n) = length {
23543                            self.write(&format!("({})", n));
23544                        }
23545                    }
23546                    Some(DialectType::SQLite) => {
23547                        // SQLite maps VARCHAR to TEXT, preserving length
23548                        self.write_keyword("TEXT");
23549                        if let Some(n) = length {
23550                            self.write(&format!("({})", n));
23551                        }
23552                    }
23553                    Some(DialectType::MySQL) if length.is_none() => {
23554                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23555                        self.write_keyword("TEXT");
23556                    }
23557                    Some(DialectType::Hive)
23558                    | Some(DialectType::Spark)
23559                    | Some(DialectType::Databricks)
23560                        if length.is_none() =>
23561                    {
23562                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23563                        self.write_keyword("STRING");
23564                    }
23565                    _ => {
23566                        self.write_keyword("VARCHAR");
23567                        if let Some(n) = length {
23568                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23569                            if *parenthesized_length {
23570                                self.write(&format!("(({}))", n));
23571                            } else {
23572                                self.write(&format!("({})", n));
23573                            }
23574                        }
23575                    }
23576                }
23577            }
23578            DataType::Text => {
23579                // Dialect-specific text type mappings
23580                match self.config.dialect {
23581                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23582                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23583                        self.write_keyword("VARCHAR(MAX)")
23584                    }
23585                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23586                    Some(DialectType::Snowflake)
23587                    | Some(DialectType::Dremio)
23588                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23589                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23590                    Some(DialectType::Presto)
23591                    | Some(DialectType::Trino)
23592                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23593                    Some(DialectType::Spark)
23594                    | Some(DialectType::Databricks)
23595                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23596                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23597                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23598                        self.write_keyword("STRING")
23599                    }
23600                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23601                    _ => self.write_keyword("TEXT"),
23602                }
23603            }
23604            DataType::TextWithLength { length } => {
23605                // TEXT(n) - dialect-specific type with length
23606                match self.config.dialect {
23607                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23608                    Some(DialectType::Hive)
23609                    | Some(DialectType::Spark)
23610                    | Some(DialectType::Databricks) => {
23611                        self.write(&format!("VARCHAR({})", length));
23612                    }
23613                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23614                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23615                    Some(DialectType::Snowflake)
23616                    | Some(DialectType::Presto)
23617                    | Some(DialectType::Trino)
23618                    | Some(DialectType::Athena)
23619                    | Some(DialectType::Drill)
23620                    | Some(DialectType::Dremio) => {
23621                        self.write(&format!("VARCHAR({})", length));
23622                    }
23623                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23624                        self.write(&format!("VARCHAR({})", length))
23625                    }
23626                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23627                        self.write(&format!("STRING({})", length))
23628                    }
23629                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23630                    _ => self.write(&format!("TEXT({})", length)),
23631                }
23632            }
23633            DataType::String { length } => {
23634                // STRING type with optional length (BigQuery STRING(n))
23635                match self.config.dialect {
23636                    Some(DialectType::ClickHouse) => {
23637                        // ClickHouse uses String with specific casing
23638                        self.write("String");
23639                        if let Some(n) = length {
23640                            self.write(&format!("({})", n));
23641                        }
23642                    }
23643                    Some(DialectType::BigQuery)
23644                    | Some(DialectType::Hive)
23645                    | Some(DialectType::Spark)
23646                    | Some(DialectType::Databricks)
23647                    | Some(DialectType::StarRocks)
23648                    | Some(DialectType::Doris) => {
23649                        self.write_keyword("STRING");
23650                        if let Some(n) = length {
23651                            self.write(&format!("({})", n));
23652                        }
23653                    }
23654                    Some(DialectType::PostgreSQL) => {
23655                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23656                        if let Some(n) = length {
23657                            self.write_keyword("VARCHAR");
23658                            self.write(&format!("({})", n));
23659                        } else {
23660                            self.write_keyword("TEXT");
23661                        }
23662                    }
23663                    Some(DialectType::Redshift) => {
23664                        // Redshift: STRING -> VARCHAR(MAX)
23665                        if let Some(n) = length {
23666                            self.write_keyword("VARCHAR");
23667                            self.write(&format!("({})", n));
23668                        } else {
23669                            self.write_keyword("VARCHAR(MAX)");
23670                        }
23671                    }
23672                    Some(DialectType::MySQL) => {
23673                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23674                        if let Some(n) = length {
23675                            self.write_keyword("VARCHAR");
23676                            self.write(&format!("({})", n));
23677                        } else {
23678                            self.write_keyword("TEXT");
23679                        }
23680                    }
23681                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23682                        // TSQL: STRING -> VARCHAR(MAX)
23683                        if let Some(n) = length {
23684                            self.write_keyword("VARCHAR");
23685                            self.write(&format!("({})", n));
23686                        } else {
23687                            self.write_keyword("VARCHAR(MAX)");
23688                        }
23689                    }
23690                    Some(DialectType::Oracle) => {
23691                        // Oracle: STRING -> CLOB
23692                        self.write_keyword("CLOB");
23693                    }
23694                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23695                        // DuckDB/Materialize uses TEXT for string types
23696                        self.write_keyword("TEXT");
23697                        if let Some(n) = length {
23698                            self.write(&format!("({})", n));
23699                        }
23700                    }
23701                    Some(DialectType::Presto)
23702                    | Some(DialectType::Trino)
23703                    | Some(DialectType::Drill)
23704                    | Some(DialectType::Dremio) => {
23705                        // Presto/Trino/Drill use VARCHAR for string types
23706                        self.write_keyword("VARCHAR");
23707                        if let Some(n) = length {
23708                            self.write(&format!("({})", n));
23709                        }
23710                    }
23711                    Some(DialectType::Snowflake) => {
23712                        // Snowflake: STRING stays as STRING (identity/DDL)
23713                        // CAST context STRING -> VARCHAR is handled in generate_cast
23714                        self.write_keyword("STRING");
23715                        if let Some(n) = length {
23716                            self.write(&format!("({})", n));
23717                        }
23718                    }
23719                    _ => {
23720                        // Default: output STRING with optional length
23721                        self.write_keyword("STRING");
23722                        if let Some(n) = length {
23723                            self.write(&format!("({})", n));
23724                        }
23725                    }
23726                }
23727            }
23728            DataType::Binary { length } => {
23729                // Dialect-specific binary type mappings
23730                match self.config.dialect {
23731                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23732                        self.write_keyword("BYTEA");
23733                        if let Some(n) = length {
23734                            self.write(&format!("({})", n));
23735                        }
23736                    }
23737                    Some(DialectType::Redshift) => {
23738                        self.write_keyword("VARBYTE");
23739                        if let Some(n) = length {
23740                            self.write(&format!("({})", n));
23741                        }
23742                    }
23743                    Some(DialectType::DuckDB)
23744                    | Some(DialectType::SQLite)
23745                    | Some(DialectType::Oracle) => {
23746                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23747                        self.write_keyword("BLOB");
23748                        if let Some(n) = length {
23749                            self.write(&format!("({})", n));
23750                        }
23751                    }
23752                    Some(DialectType::Presto)
23753                    | Some(DialectType::Trino)
23754                    | Some(DialectType::Athena)
23755                    | Some(DialectType::Drill)
23756                    | Some(DialectType::Dremio) => {
23757                        // These dialects map BINARY to VARBINARY
23758                        self.write_keyword("VARBINARY");
23759                        if let Some(n) = length {
23760                            self.write(&format!("({})", n));
23761                        }
23762                    }
23763                    Some(DialectType::ClickHouse) => {
23764                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23765                        if self.clickhouse_nullable_depth < 0 {
23766                            self.write("BINARY");
23767                        } else {
23768                            self.write("Nullable(BINARY");
23769                        }
23770                        if let Some(n) = length {
23771                            self.write(&format!("({})", n));
23772                        }
23773                        if self.clickhouse_nullable_depth >= 0 {
23774                            self.write(")");
23775                        }
23776                    }
23777                    _ => {
23778                        self.write_keyword("BINARY");
23779                        if let Some(n) = length {
23780                            self.write(&format!("({})", n));
23781                        }
23782                    }
23783                }
23784            }
23785            DataType::VarBinary { length } => {
23786                // Dialect-specific varbinary type mappings
23787                match self.config.dialect {
23788                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23789                        self.write_keyword("BYTEA");
23790                        if let Some(n) = length {
23791                            self.write(&format!("({})", n));
23792                        }
23793                    }
23794                    Some(DialectType::Redshift) => {
23795                        self.write_keyword("VARBYTE");
23796                        if let Some(n) = length {
23797                            self.write(&format!("({})", n));
23798                        }
23799                    }
23800                    Some(DialectType::DuckDB)
23801                    | Some(DialectType::SQLite)
23802                    | Some(DialectType::Oracle) => {
23803                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23804                        self.write_keyword("BLOB");
23805                        if let Some(n) = length {
23806                            self.write(&format!("({})", n));
23807                        }
23808                    }
23809                    Some(DialectType::Exasol) => {
23810                        // Exasol maps VARBINARY to VARCHAR
23811                        self.write_keyword("VARCHAR");
23812                    }
23813                    Some(DialectType::Spark)
23814                    | Some(DialectType::Hive)
23815                    | Some(DialectType::Databricks) => {
23816                        // Spark/Hive use BINARY instead of VARBINARY
23817                        self.write_keyword("BINARY");
23818                        if let Some(n) = length {
23819                            self.write(&format!("({})", n));
23820                        }
23821                    }
23822                    Some(DialectType::ClickHouse) => {
23823                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23824                        self.write_clickhouse_type("String");
23825                    }
23826                    _ => {
23827                        self.write_keyword("VARBINARY");
23828                        if let Some(n) = length {
23829                            self.write(&format!("({})", n));
23830                        }
23831                    }
23832                }
23833            }
23834            DataType::Blob => {
23835                // Dialect-specific blob type mappings
23836                match self.config.dialect {
23837                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23838                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23839                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23840                        self.write_keyword("VARBINARY")
23841                    }
23842                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23843                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23844                    Some(DialectType::Presto)
23845                    | Some(DialectType::Trino)
23846                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23847                    Some(DialectType::DuckDB) => {
23848                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23849                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23850                        self.write_keyword("VARBINARY");
23851                    }
23852                    Some(DialectType::Spark)
23853                    | Some(DialectType::Databricks)
23854                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23855                    Some(DialectType::ClickHouse) => {
23856                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23857                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23858                        // This matches Python sqlglot behavior.
23859                        self.write("Nullable(String)");
23860                    }
23861                    _ => self.write_keyword("BLOB"),
23862                }
23863            }
23864            DataType::Bit { length } => {
23865                // Dialect-specific bit type mappings
23866                match self.config.dialect {
23867                    Some(DialectType::Dremio)
23868                    | Some(DialectType::Spark)
23869                    | Some(DialectType::Databricks)
23870                    | Some(DialectType::Hive)
23871                    | Some(DialectType::Snowflake)
23872                    | Some(DialectType::BigQuery)
23873                    | Some(DialectType::Presto)
23874                    | Some(DialectType::Trino)
23875                    | Some(DialectType::ClickHouse)
23876                    | Some(DialectType::Redshift) => {
23877                        // These dialects don't support BIT type, use BOOLEAN
23878                        self.write_keyword("BOOLEAN");
23879                    }
23880                    _ => {
23881                        self.write_keyword("BIT");
23882                        if let Some(n) = length {
23883                            self.write(&format!("({})", n));
23884                        }
23885                    }
23886                }
23887            }
23888            DataType::VarBit { length } => {
23889                self.write_keyword("VARBIT");
23890                if let Some(n) = length {
23891                    self.write(&format!("({})", n));
23892                }
23893            }
23894            DataType::Date => self.write_keyword("DATE"),
23895            DataType::Time {
23896                precision,
23897                timezone,
23898            } => {
23899                if *timezone {
23900                    // Dialect-specific TIME WITH TIME ZONE output
23901                    match self.config.dialect {
23902                        Some(DialectType::DuckDB) => {
23903                            // DuckDB: TIMETZ (drops precision)
23904                            self.write_keyword("TIMETZ");
23905                        }
23906                        Some(DialectType::PostgreSQL) => {
23907                            // PostgreSQL: TIMETZ or TIMETZ(p)
23908                            self.write_keyword("TIMETZ");
23909                            if let Some(p) = precision {
23910                                self.write(&format!("({})", p));
23911                            }
23912                        }
23913                        _ => {
23914                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23915                            self.write_keyword("TIME");
23916                            if let Some(p) = precision {
23917                                self.write(&format!("({})", p));
23918                            }
23919                            self.write_keyword(" WITH TIME ZONE");
23920                        }
23921                    }
23922                } else {
23923                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23924                    if matches!(
23925                        self.config.dialect,
23926                        Some(DialectType::Spark)
23927                            | Some(DialectType::Databricks)
23928                            | Some(DialectType::Hive)
23929                    ) {
23930                        self.write_keyword("TIMESTAMP");
23931                    } else {
23932                        self.write_keyword("TIME");
23933                        if let Some(p) = precision {
23934                            self.write(&format!("({})", p));
23935                        }
23936                    }
23937                }
23938            }
23939            DataType::Timestamp {
23940                precision,
23941                timezone,
23942            } => {
23943                // Dialect-specific timestamp type mappings
23944                match self.config.dialect {
23945                    Some(DialectType::ClickHouse) => {
23946                        self.write("DateTime");
23947                        if let Some(p) = precision {
23948                            self.write(&format!("({})", p));
23949                        }
23950                    }
23951                    Some(DialectType::TSQL) => {
23952                        if *timezone {
23953                            self.write_keyword("DATETIMEOFFSET");
23954                        } else {
23955                            self.write_keyword("DATETIME2");
23956                        }
23957                        if let Some(p) = precision {
23958                            self.write(&format!("({})", p));
23959                        }
23960                    }
23961                    Some(DialectType::MySQL) => {
23962                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23963                        self.write_keyword("TIMESTAMP");
23964                        if let Some(p) = precision {
23965                            self.write(&format!("({})", p));
23966                        }
23967                    }
23968                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23969                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23970                        self.write_keyword("DATETIME");
23971                        if let Some(p) = precision {
23972                            self.write(&format!("({})", p));
23973                        }
23974                    }
23975                    Some(DialectType::BigQuery) => {
23976                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23977                        if *timezone {
23978                            self.write_keyword("TIMESTAMP");
23979                        } else {
23980                            self.write_keyword("DATETIME");
23981                        }
23982                    }
23983                    Some(DialectType::DuckDB) => {
23984                        // DuckDB: TIMESTAMPTZ shorthand
23985                        if *timezone {
23986                            self.write_keyword("TIMESTAMPTZ");
23987                        } else {
23988                            self.write_keyword("TIMESTAMP");
23989                            if let Some(p) = precision {
23990                                self.write(&format!("({})", p));
23991                            }
23992                        }
23993                    }
23994                    _ => {
23995                        if *timezone && !self.config.tz_to_with_time_zone {
23996                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23997                            self.write_keyword("TIMESTAMPTZ");
23998                            if let Some(p) = precision {
23999                                self.write(&format!("({})", p));
24000                            }
24001                        } else {
24002                            self.write_keyword("TIMESTAMP");
24003                            if let Some(p) = precision {
24004                                self.write(&format!("({})", p));
24005                            }
24006                            if *timezone {
24007                                self.write_space();
24008                                self.write_keyword("WITH TIME ZONE");
24009                            }
24010                        }
24011                    }
24012                }
24013            }
24014            DataType::Interval { unit, to } => {
24015                self.write_keyword("INTERVAL");
24016                if let Some(u) = unit {
24017                    self.write_space();
24018                    self.write_keyword(u);
24019                }
24020                // Handle range intervals like DAY TO HOUR
24021                if let Some(t) = to {
24022                    self.write_space();
24023                    self.write_keyword("TO");
24024                    self.write_space();
24025                    self.write_keyword(t);
24026                }
24027            }
24028            DataType::Json => {
24029                // Dialect-specific JSON type mappings
24030                match self.config.dialect {
24031                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24032                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24033                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24034                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24035                    _ => self.write_keyword("JSON"),
24036                }
24037            }
24038            DataType::JsonB => {
24039                // JSONB is PostgreSQL specific, but Doris also supports it
24040                match self.config.dialect {
24041                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24042                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24043                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24044                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24045                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24046                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24047                }
24048            }
24049            DataType::Uuid => {
24050                // Dialect-specific UUID type mappings
24051                match self.config.dialect {
24052                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24053                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24054                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24055                    Some(DialectType::BigQuery)
24056                    | Some(DialectType::Spark)
24057                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24058                    _ => self.write_keyword("UUID"),
24059                }
24060            }
24061            DataType::Array {
24062                element_type,
24063                dimension,
24064            } => {
24065                // Dialect-specific array syntax
24066                match self.config.dialect {
24067                    Some(DialectType::PostgreSQL)
24068                    | Some(DialectType::Redshift)
24069                    | Some(DialectType::DuckDB) => {
24070                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24071                        self.generate_data_type(element_type)?;
24072                        if let Some(dim) = dimension {
24073                            self.write(&format!("[{}]", dim));
24074                        } else {
24075                            self.write("[]");
24076                        }
24077                    }
24078                    Some(DialectType::BigQuery) => {
24079                        self.write_keyword("ARRAY<");
24080                        self.generate_data_type(element_type)?;
24081                        self.write(">");
24082                    }
24083                    Some(DialectType::Snowflake)
24084                    | Some(DialectType::Presto)
24085                    | Some(DialectType::Trino)
24086                    | Some(DialectType::ClickHouse) => {
24087                        // These dialects use Array(TYPE) parentheses syntax
24088                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24089                            self.write("Array(");
24090                        } else {
24091                            self.write_keyword("ARRAY(");
24092                        }
24093                        self.generate_data_type(element_type)?;
24094                        self.write(")");
24095                    }
24096                    Some(DialectType::TSQL)
24097                    | Some(DialectType::MySQL)
24098                    | Some(DialectType::Oracle) => {
24099                        // These dialects don't have native array types
24100                        // Fall back to JSON or use native workarounds
24101                        match self.config.dialect {
24102                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24103                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24104                            _ => self.write_keyword("JSON"),
24105                        }
24106                    }
24107                    _ => {
24108                        // Default: use angle bracket syntax (ARRAY<T>)
24109                        self.write_keyword("ARRAY<");
24110                        self.generate_data_type(element_type)?;
24111                        self.write(">");
24112                    }
24113                }
24114            }
24115            DataType::List { element_type } => {
24116                // Materialize: element_type LIST (postfix syntax)
24117                self.generate_data_type(element_type)?;
24118                self.write_keyword(" LIST");
24119            }
24120            DataType::Map {
24121                key_type,
24122                value_type,
24123            } => {
24124                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24125                match self.config.dialect {
24126                    Some(DialectType::Materialize) => {
24127                        // Materialize: MAP[key_type => value_type]
24128                        self.write_keyword("MAP[");
24129                        self.generate_data_type(key_type)?;
24130                        self.write(" => ");
24131                        self.generate_data_type(value_type)?;
24132                        self.write("]");
24133                    }
24134                    Some(DialectType::Snowflake)
24135                    | Some(DialectType::RisingWave)
24136                    | Some(DialectType::DuckDB)
24137                    | Some(DialectType::Presto)
24138                    | Some(DialectType::Trino)
24139                    | Some(DialectType::Athena) => {
24140                        self.write_keyword("MAP(");
24141                        self.generate_data_type(key_type)?;
24142                        self.write(", ");
24143                        self.generate_data_type(value_type)?;
24144                        self.write(")");
24145                    }
24146                    Some(DialectType::ClickHouse) => {
24147                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24148                        // Key types must NOT be wrapped in Nullable
24149                        self.write("Map(");
24150                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24151                        self.generate_data_type(key_type)?;
24152                        self.clickhouse_nullable_depth = 0;
24153                        self.write(", ");
24154                        self.generate_data_type(value_type)?;
24155                        self.write(")");
24156                    }
24157                    _ => {
24158                        self.write_keyword("MAP<");
24159                        self.generate_data_type(key_type)?;
24160                        self.write(", ");
24161                        self.generate_data_type(value_type)?;
24162                        self.write(">");
24163                    }
24164                }
24165            }
24166            DataType::Vector {
24167                element_type,
24168                dimension,
24169            } => {
24170                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24171                    // SingleStore format: VECTOR(dimension, type_alias)
24172                    self.write_keyword("VECTOR(");
24173                    if let Some(dim) = dimension {
24174                        self.write(&dim.to_string());
24175                    }
24176                    // Map type back to SingleStore alias
24177                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24178                        DataType::TinyInt { .. } => Some("I8"),
24179                        DataType::SmallInt { .. } => Some("I16"),
24180                        DataType::Int { .. } => Some("I32"),
24181                        DataType::BigInt { .. } => Some("I64"),
24182                        DataType::Float { .. } => Some("F32"),
24183                        DataType::Double { .. } => Some("F64"),
24184                        _ => None,
24185                    });
24186                    if let Some(alias) = type_alias {
24187                        if dimension.is_some() {
24188                            self.write(", ");
24189                        }
24190                        self.write(alias);
24191                    }
24192                    self.write(")");
24193                } else {
24194                    // Snowflake format: VECTOR(type, dimension)
24195                    self.write_keyword("VECTOR(");
24196                    if let Some(ref et) = element_type {
24197                        self.generate_data_type(et)?;
24198                        if dimension.is_some() {
24199                            self.write(", ");
24200                        }
24201                    }
24202                    if let Some(dim) = dimension {
24203                        self.write(&dim.to_string());
24204                    }
24205                    self.write(")");
24206                }
24207            }
24208            DataType::Object { fields, modifier } => {
24209                self.write_keyword("OBJECT(");
24210                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24211                    if i > 0 {
24212                        self.write(", ");
24213                    }
24214                    self.write(name);
24215                    self.write(" ");
24216                    self.generate_data_type(dt)?;
24217                    if *not_null {
24218                        self.write_keyword(" NOT NULL");
24219                    }
24220                }
24221                self.write(")");
24222                if let Some(mod_str) = modifier {
24223                    self.write(" ");
24224                    self.write_keyword(mod_str);
24225                }
24226            }
24227            DataType::Struct { fields, nested } => {
24228                // Dialect-specific struct type mappings
24229                match self.config.dialect {
24230                    Some(DialectType::Snowflake) => {
24231                        // Snowflake maps STRUCT to OBJECT
24232                        self.write_keyword("OBJECT(");
24233                        for (i, field) in fields.iter().enumerate() {
24234                            if i > 0 {
24235                                self.write(", ");
24236                            }
24237                            if !field.name.is_empty() {
24238                                self.write(&field.name);
24239                                self.write(" ");
24240                            }
24241                            self.generate_data_type(&field.data_type)?;
24242                        }
24243                        self.write(")");
24244                    }
24245                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24246                        // Presto/Trino use ROW(name TYPE, ...) syntax
24247                        self.write_keyword("ROW(");
24248                        for (i, field) in fields.iter().enumerate() {
24249                            if i > 0 {
24250                                self.write(", ");
24251                            }
24252                            if !field.name.is_empty() {
24253                                self.write(&field.name);
24254                                self.write(" ");
24255                            }
24256                            self.generate_data_type(&field.data_type)?;
24257                        }
24258                        self.write(")");
24259                    }
24260                    Some(DialectType::DuckDB) => {
24261                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24262                        self.write_keyword("STRUCT(");
24263                        for (i, field) in fields.iter().enumerate() {
24264                            if i > 0 {
24265                                self.write(", ");
24266                            }
24267                            if !field.name.is_empty() {
24268                                self.write(&field.name);
24269                                self.write(" ");
24270                            }
24271                            self.generate_data_type(&field.data_type)?;
24272                        }
24273                        self.write(")");
24274                    }
24275                    Some(DialectType::ClickHouse) => {
24276                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24277                        self.write("Tuple(");
24278                        for (i, field) in fields.iter().enumerate() {
24279                            if i > 0 {
24280                                self.write(", ");
24281                            }
24282                            if !field.name.is_empty() {
24283                                self.write(&field.name);
24284                                self.write(" ");
24285                            }
24286                            self.generate_data_type(&field.data_type)?;
24287                        }
24288                        self.write(")");
24289                    }
24290                    Some(DialectType::SingleStore) => {
24291                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24292                        self.write_keyword("RECORD(");
24293                        for (i, field) in fields.iter().enumerate() {
24294                            if i > 0 {
24295                                self.write(", ");
24296                            }
24297                            if !field.name.is_empty() {
24298                                self.write(&field.name);
24299                                self.write(" ");
24300                            }
24301                            self.generate_data_type(&field.data_type)?;
24302                        }
24303                        self.write(")");
24304                    }
24305                    _ => {
24306                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24307                        let force_angle_brackets = matches!(
24308                            self.config.dialect,
24309                            Some(DialectType::Hive)
24310                                | Some(DialectType::Spark)
24311                                | Some(DialectType::Databricks)
24312                        );
24313                        if *nested && !force_angle_brackets {
24314                            self.write_keyword("STRUCT(");
24315                            for (i, field) in fields.iter().enumerate() {
24316                                if i > 0 {
24317                                    self.write(", ");
24318                                }
24319                                if !field.name.is_empty() {
24320                                    self.write(&field.name);
24321                                    self.write(" ");
24322                                }
24323                                self.generate_data_type(&field.data_type)?;
24324                            }
24325                            self.write(")");
24326                        } else {
24327                            self.write_keyword("STRUCT<");
24328                            for (i, field) in fields.iter().enumerate() {
24329                                if i > 0 {
24330                                    self.write(", ");
24331                                }
24332                                if !field.name.is_empty() {
24333                                    // Named field: name TYPE (with configurable separator for Hive)
24334                                    self.write(&field.name);
24335                                    self.write(self.config.struct_field_sep);
24336                                }
24337                                // For anonymous fields, just output the type
24338                                self.generate_data_type(&field.data_type)?;
24339                                // Spark/Databricks: Output COMMENT clause if present
24340                                if let Some(comment) = &field.comment {
24341                                    self.write(" COMMENT '");
24342                                    self.write(comment);
24343                                    self.write("'");
24344                                }
24345                                // BigQuery: Output OPTIONS clause if present
24346                                if !field.options.is_empty() {
24347                                    self.write(" ");
24348                                    self.generate_options_clause(&field.options)?;
24349                                }
24350                            }
24351                            self.write(">");
24352                        }
24353                    }
24354                }
24355            }
24356            DataType::Enum {
24357                values,
24358                assignments,
24359            } => {
24360                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24361                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24362                if self.config.dialect == Some(DialectType::ClickHouse) {
24363                    self.write("Enum(");
24364                } else {
24365                    self.write_keyword("ENUM(");
24366                }
24367                for (i, val) in values.iter().enumerate() {
24368                    if i > 0 {
24369                        self.write(", ");
24370                    }
24371                    self.write("'");
24372                    self.write(val);
24373                    self.write("'");
24374                    if let Some(Some(assignment)) = assignments.get(i) {
24375                        self.write(" = ");
24376                        self.write(assignment);
24377                    }
24378                }
24379                self.write(")");
24380            }
24381            DataType::Set { values } => {
24382                // MySQL SET type: SET('a', 'b', 'c')
24383                self.write_keyword("SET(");
24384                for (i, val) in values.iter().enumerate() {
24385                    if i > 0 {
24386                        self.write(", ");
24387                    }
24388                    self.write("'");
24389                    self.write(val);
24390                    self.write("'");
24391                }
24392                self.write(")");
24393            }
24394            DataType::Union { fields } => {
24395                // DuckDB UNION type: UNION(num INT, str TEXT)
24396                self.write_keyword("UNION(");
24397                for (i, (name, dt)) in fields.iter().enumerate() {
24398                    if i > 0 {
24399                        self.write(", ");
24400                    }
24401                    if !name.is_empty() {
24402                        self.write(name);
24403                        self.write(" ");
24404                    }
24405                    self.generate_data_type(dt)?;
24406                }
24407                self.write(")");
24408            }
24409            DataType::Nullable { inner } => {
24410                // ClickHouse: Nullable(T), other dialects: just the inner type
24411                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24412                    self.write("Nullable(");
24413                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24414                    let saved_depth = self.clickhouse_nullable_depth;
24415                    self.clickhouse_nullable_depth = -1;
24416                    self.generate_data_type(inner)?;
24417                    self.clickhouse_nullable_depth = saved_depth;
24418                    self.write(")");
24419                } else {
24420                    // Map ClickHouse-specific custom type names to standard types
24421                    match inner.as_ref() {
24422                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24423                            self.generate_data_type(&DataType::Timestamp {
24424                                precision: None,
24425                                timezone: false,
24426                            })?;
24427                        }
24428                        _ => {
24429                            self.generate_data_type(inner)?;
24430                        }
24431                    }
24432                }
24433            }
24434            DataType::Custom { name } => {
24435                // Handle dialect-specific type transformations
24436                let name_upper = name.to_ascii_uppercase();
24437                match self.config.dialect {
24438                    Some(DialectType::ClickHouse) => {
24439                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24440                            (name_upper[..idx].to_string(), &name[idx..])
24441                        } else {
24442                            (name_upper.clone(), "")
24443                        };
24444                        let mapped = match base_upper.as_str() {
24445                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24446                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24447                            "DATETIME64" => "DateTime64",
24448                            "DATE32" => "Date32",
24449                            "INT" => "Int32",
24450                            "MEDIUMINT" => "Int32",
24451                            "INT8" => "Int8",
24452                            "INT16" => "Int16",
24453                            "INT32" => "Int32",
24454                            "INT64" => "Int64",
24455                            "INT128" => "Int128",
24456                            "INT256" => "Int256",
24457                            "UINT8" => "UInt8",
24458                            "UINT16" => "UInt16",
24459                            "UINT32" => "UInt32",
24460                            "UINT64" => "UInt64",
24461                            "UINT128" => "UInt128",
24462                            "UINT256" => "UInt256",
24463                            "FLOAT32" => "Float32",
24464                            "FLOAT64" => "Float64",
24465                            "DECIMAL32" => "Decimal32",
24466                            "DECIMAL64" => "Decimal64",
24467                            "DECIMAL128" => "Decimal128",
24468                            "DECIMAL256" => "Decimal256",
24469                            "ENUM" => "Enum",
24470                            "ENUM8" => "Enum8",
24471                            "ENUM16" => "Enum16",
24472                            "FIXEDSTRING" => "FixedString",
24473                            "NESTED" => "Nested",
24474                            "LOWCARDINALITY" => "LowCardinality",
24475                            "NULLABLE" => "Nullable",
24476                            "IPV4" => "IPv4",
24477                            "IPV6" => "IPv6",
24478                            "POINT" => "Point",
24479                            "RING" => "Ring",
24480                            "LINESTRING" => "LineString",
24481                            "MULTILINESTRING" => "MultiLineString",
24482                            "POLYGON" => "Polygon",
24483                            "MULTIPOLYGON" => "MultiPolygon",
24484                            "AGGREGATEFUNCTION" => "AggregateFunction",
24485                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24486                            "DYNAMIC" => "Dynamic",
24487                            _ => "",
24488                        };
24489                        if mapped.is_empty() {
24490                            self.write(name);
24491                        } else {
24492                            self.write(mapped);
24493                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
24494                                && !suffix.is_empty()
24495                            {
24496                                let escaped_suffix = suffix
24497                                    .replace('\\', "\\\\")
24498                                    .replace('\t', "\\t")
24499                                    .replace('\n', "\\n")
24500                                    .replace('\r', "\\r");
24501                                self.write(&escaped_suffix);
24502                            } else {
24503                                self.write(suffix);
24504                            }
24505                        }
24506                    }
24507                    Some(DialectType::MySQL)
24508                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24509                    {
24510                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24511                        self.write_keyword("TIMESTAMP");
24512                    }
24513                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
24514                        self.write_keyword("SQL_VARIANT");
24515                    }
24516                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
24517                        self.write_keyword("DECIMAL(38, 5)");
24518                    }
24519                    Some(DialectType::Exasol) => {
24520                        // Exasol type mappings for custom types
24521                        match name_upper.as_str() {
24522                            // Binary types → VARCHAR
24523                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
24524                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
24525                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
24526                            // Integer types
24527                            "MEDIUMINT" => self.write_keyword("INT"),
24528                            // Decimal types → DECIMAL
24529                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
24530                                self.write_keyword("DECIMAL")
24531                            }
24532                            // Timestamp types
24533                            "DATETIME" => self.write_keyword("TIMESTAMP"),
24534                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
24535                            _ => self.write(name),
24536                        }
24537                    }
24538                    Some(DialectType::Dremio) => {
24539                        // Dremio type mappings for custom types
24540                        match name_upper.as_str() {
24541                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
24542                            "ARRAY" => self.write_keyword("LIST"),
24543                            "NCHAR" => self.write_keyword("VARCHAR"),
24544                            _ => self.write(name),
24545                        }
24546                    }
24547                    // Map dialect-specific custom types to standard SQL types for other dialects
24548                    _ => {
24549                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
24550                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
24551                            (name_upper[..idx].to_string(), Some(&name[idx..]))
24552                        } else {
24553                            (name_upper.clone(), None)
24554                        };
24555
24556                        match base_upper.as_str() {
24557                            "INT64"
24558                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24559                            {
24560                                self.write_keyword("BIGINT");
24561                            }
24562                            "FLOAT64"
24563                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24564                            {
24565                                self.write_keyword("DOUBLE");
24566                            }
24567                            "BOOL"
24568                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24569                            {
24570                                self.write_keyword("BOOLEAN");
24571                            }
24572                            "BYTES"
24573                                if matches!(
24574                                    self.config.dialect,
24575                                    Some(DialectType::Spark)
24576                                        | Some(DialectType::Hive)
24577                                        | Some(DialectType::Databricks)
24578                                ) =>
24579                            {
24580                                self.write_keyword("BINARY");
24581                            }
24582                            "BYTES"
24583                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24584                            {
24585                                self.write_keyword("VARBINARY");
24586                            }
24587                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24588                            "DATETIME2" | "SMALLDATETIME"
24589                                if !matches!(
24590                                    self.config.dialect,
24591                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24592                                ) =>
24593                            {
24594                                // PostgreSQL preserves precision, others don't
24595                                if matches!(
24596                                    self.config.dialect,
24597                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24598                                ) {
24599                                    self.write_keyword("TIMESTAMP");
24600                                    if let Some(args) = _args_str {
24601                                        self.write(args);
24602                                    }
24603                                } else {
24604                                    self.write_keyword("TIMESTAMP");
24605                                }
24606                            }
24607                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24608                            "DATETIMEOFFSET"
24609                                if !matches!(
24610                                    self.config.dialect,
24611                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24612                                ) =>
24613                            {
24614                                if matches!(
24615                                    self.config.dialect,
24616                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24617                                ) {
24618                                    self.write_keyword("TIMESTAMPTZ");
24619                                    if let Some(args) = _args_str {
24620                                        self.write(args);
24621                                    }
24622                                } else {
24623                                    self.write_keyword("TIMESTAMPTZ");
24624                                }
24625                            }
24626                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24627                            "UNIQUEIDENTIFIER"
24628                                if !matches!(
24629                                    self.config.dialect,
24630                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24631                                ) =>
24632                            {
24633                                match self.config.dialect {
24634                                    Some(DialectType::Spark)
24635                                    | Some(DialectType::Databricks)
24636                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24637                                    _ => self.write_keyword("UUID"),
24638                                }
24639                            }
24640                            // TSQL BIT -> BOOLEAN for most dialects
24641                            "BIT"
24642                                if !matches!(
24643                                    self.config.dialect,
24644                                    Some(DialectType::TSQL)
24645                                        | Some(DialectType::Fabric)
24646                                        | Some(DialectType::PostgreSQL)
24647                                        | Some(DialectType::MySQL)
24648                                        | Some(DialectType::DuckDB)
24649                                ) =>
24650                            {
24651                                self.write_keyword("BOOLEAN");
24652                            }
24653                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24654                            "NVARCHAR"
24655                                if !matches!(
24656                                    self.config.dialect,
24657                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24658                                ) =>
24659                            {
24660                                match self.config.dialect {
24661                                    Some(DialectType::Oracle) => {
24662                                        // Oracle: NVARCHAR -> NVARCHAR2
24663                                        self.write_keyword("NVARCHAR2");
24664                                        if let Some(args) = _args_str {
24665                                            self.write(args);
24666                                        }
24667                                    }
24668                                    Some(DialectType::BigQuery) => {
24669                                        // BigQuery: NVARCHAR -> STRING
24670                                        self.write_keyword("STRING");
24671                                    }
24672                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24673                                        self.write_keyword("TEXT");
24674                                        if let Some(args) = _args_str {
24675                                            self.write(args);
24676                                        }
24677                                    }
24678                                    Some(DialectType::Hive) => {
24679                                        // Hive: NVARCHAR -> STRING
24680                                        self.write_keyword("STRING");
24681                                    }
24682                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24683                                        if _args_str.is_some() {
24684                                            self.write_keyword("VARCHAR");
24685                                            self.write(_args_str.unwrap());
24686                                        } else {
24687                                            self.write_keyword("STRING");
24688                                        }
24689                                    }
24690                                    _ => {
24691                                        self.write_keyword("VARCHAR");
24692                                        if let Some(args) = _args_str {
24693                                            self.write(args);
24694                                        }
24695                                    }
24696                                }
24697                            }
24698                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24699                            "NCHAR"
24700                                if !matches!(
24701                                    self.config.dialect,
24702                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24703                                ) =>
24704                            {
24705                                match self.config.dialect {
24706                                    Some(DialectType::Oracle) => {
24707                                        // Oracle natively supports NCHAR
24708                                        self.write_keyword("NCHAR");
24709                                        if let Some(args) = _args_str {
24710                                            self.write(args);
24711                                        }
24712                                    }
24713                                    Some(DialectType::BigQuery) => {
24714                                        // BigQuery: NCHAR -> STRING
24715                                        self.write_keyword("STRING");
24716                                    }
24717                                    Some(DialectType::Hive) => {
24718                                        // Hive: NCHAR -> STRING
24719                                        self.write_keyword("STRING");
24720                                    }
24721                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24722                                        self.write_keyword("TEXT");
24723                                        if let Some(args) = _args_str {
24724                                            self.write(args);
24725                                        }
24726                                    }
24727                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24728                                        if _args_str.is_some() {
24729                                            self.write_keyword("CHAR");
24730                                            self.write(_args_str.unwrap());
24731                                        } else {
24732                                            self.write_keyword("STRING");
24733                                        }
24734                                    }
24735                                    _ => {
24736                                        self.write_keyword("CHAR");
24737                                        if let Some(args) = _args_str {
24738                                            self.write(args);
24739                                        }
24740                                    }
24741                                }
24742                            }
24743                            // MySQL text variant types -> map to appropriate target type
24744                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24745                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24746                                Some(DialectType::MySQL)
24747                                | Some(DialectType::SingleStore)
24748                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24749                                Some(DialectType::Spark)
24750                                | Some(DialectType::Databricks)
24751                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24752                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24753                                Some(DialectType::Presto)
24754                                | Some(DialectType::Trino)
24755                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24756                                Some(DialectType::Snowflake)
24757                                | Some(DialectType::Redshift)
24758                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24759                                _ => self.write_keyword("TEXT"),
24760                            },
24761                            // MySQL blob variant types -> map to appropriate target type
24762                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24763                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24764                                Some(DialectType::MySQL)
24765                                | Some(DialectType::SingleStore)
24766                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24767                                Some(DialectType::Spark)
24768                                | Some(DialectType::Databricks)
24769                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24770                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24771                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24772                                Some(DialectType::Presto)
24773                                | Some(DialectType::Trino)
24774                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24775                                Some(DialectType::Snowflake)
24776                                | Some(DialectType::Redshift)
24777                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24778                                _ => self.write_keyword("BLOB"),
24779                            },
24780                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24781                            "LONGVARCHAR" => match self.config.dialect {
24782                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24783                                _ => self.write_keyword("VARCHAR"),
24784                            },
24785                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24786                            "DATETIME" => {
24787                                match self.config.dialect {
24788                                    Some(DialectType::MySQL)
24789                                    | Some(DialectType::Doris)
24790                                    | Some(DialectType::StarRocks)
24791                                    | Some(DialectType::TSQL)
24792                                    | Some(DialectType::Fabric)
24793                                    | Some(DialectType::BigQuery)
24794                                    | Some(DialectType::SQLite)
24795                                    | Some(DialectType::Snowflake) => {
24796                                        self.write_keyword("DATETIME");
24797                                        if let Some(args) = _args_str {
24798                                            self.write(args);
24799                                        }
24800                                    }
24801                                    Some(_) => {
24802                                        // Only map to TIMESTAMP when we have a specific target dialect
24803                                        self.write_keyword("TIMESTAMP");
24804                                        if let Some(args) = _args_str {
24805                                            self.write(args);
24806                                        }
24807                                    }
24808                                    None => {
24809                                        // No dialect - preserve original
24810                                        self.write(name);
24811                                    }
24812                                }
24813                            }
24814                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24815                            "VARCHAR2"
24816                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24817                            {
24818                                match self.config.dialect {
24819                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24820                                        self.write_keyword("TEXT");
24821                                    }
24822                                    Some(DialectType::Hive)
24823                                    | Some(DialectType::Spark)
24824                                    | Some(DialectType::Databricks)
24825                                    | Some(DialectType::BigQuery)
24826                                    | Some(DialectType::ClickHouse)
24827                                    | Some(DialectType::StarRocks)
24828                                    | Some(DialectType::Doris) => {
24829                                        self.write_keyword("STRING");
24830                                    }
24831                                    _ => {
24832                                        self.write_keyword("VARCHAR");
24833                                        if let Some(args) = _args_str {
24834                                            self.write(args);
24835                                        }
24836                                    }
24837                                }
24838                            }
24839                            "NVARCHAR2"
24840                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24841                            {
24842                                match self.config.dialect {
24843                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24844                                        self.write_keyword("TEXT");
24845                                    }
24846                                    Some(DialectType::Hive)
24847                                    | Some(DialectType::Spark)
24848                                    | Some(DialectType::Databricks)
24849                                    | Some(DialectType::BigQuery)
24850                                    | Some(DialectType::ClickHouse)
24851                                    | Some(DialectType::StarRocks)
24852                                    | Some(DialectType::Doris) => {
24853                                        self.write_keyword("STRING");
24854                                    }
24855                                    _ => {
24856                                        self.write_keyword("VARCHAR");
24857                                        if let Some(args) = _args_str {
24858                                            self.write(args);
24859                                        }
24860                                    }
24861                                }
24862                            }
24863                            _ => self.write(name),
24864                        }
24865                    }
24866                }
24867            }
24868            DataType::Geometry { subtype, srid } => {
24869                // Dialect-specific geometry type mappings
24870                match self.config.dialect {
24871                    Some(DialectType::MySQL) => {
24872                        // MySQL uses POINT SRID 4326 syntax for specific types
24873                        if let Some(sub) = subtype {
24874                            self.write_keyword(sub);
24875                            if let Some(s) = srid {
24876                                self.write(" SRID ");
24877                                self.write(&s.to_string());
24878                            }
24879                        } else {
24880                            self.write_keyword("GEOMETRY");
24881                        }
24882                    }
24883                    Some(DialectType::BigQuery) => {
24884                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24885                        self.write_keyword("GEOGRAPHY");
24886                    }
24887                    Some(DialectType::Teradata) => {
24888                        // Teradata uses ST_GEOMETRY
24889                        self.write_keyword("ST_GEOMETRY");
24890                        if subtype.is_some() || srid.is_some() {
24891                            self.write("(");
24892                            if let Some(sub) = subtype {
24893                                self.write_keyword(sub);
24894                            }
24895                            if let Some(s) = srid {
24896                                if subtype.is_some() {
24897                                    self.write(", ");
24898                                }
24899                                self.write(&s.to_string());
24900                            }
24901                            self.write(")");
24902                        }
24903                    }
24904                    _ => {
24905                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24906                        self.write_keyword("GEOMETRY");
24907                        if subtype.is_some() || srid.is_some() {
24908                            self.write("(");
24909                            if let Some(sub) = subtype {
24910                                self.write_keyword(sub);
24911                            }
24912                            if let Some(s) = srid {
24913                                if subtype.is_some() {
24914                                    self.write(", ");
24915                                }
24916                                self.write(&s.to_string());
24917                            }
24918                            self.write(")");
24919                        }
24920                    }
24921                }
24922            }
24923            DataType::Geography { subtype, srid } => {
24924                // Dialect-specific geography type mappings
24925                match self.config.dialect {
24926                    Some(DialectType::MySQL) => {
24927                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24928                        if let Some(sub) = subtype {
24929                            self.write_keyword(sub);
24930                        } else {
24931                            self.write_keyword("GEOMETRY");
24932                        }
24933                        // Geography implies SRID 4326 (WGS84)
24934                        let effective_srid = srid.unwrap_or(4326);
24935                        self.write(" SRID ");
24936                        self.write(&effective_srid.to_string());
24937                    }
24938                    Some(DialectType::BigQuery) => {
24939                        // BigQuery uses simple GEOGRAPHY without parameters
24940                        self.write_keyword("GEOGRAPHY");
24941                    }
24942                    Some(DialectType::Snowflake) => {
24943                        // Snowflake uses GEOGRAPHY without parameters
24944                        self.write_keyword("GEOGRAPHY");
24945                    }
24946                    _ => {
24947                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24948                        self.write_keyword("GEOGRAPHY");
24949                        if subtype.is_some() || srid.is_some() {
24950                            self.write("(");
24951                            if let Some(sub) = subtype {
24952                                self.write_keyword(sub);
24953                            }
24954                            if let Some(s) = srid {
24955                                if subtype.is_some() {
24956                                    self.write(", ");
24957                                }
24958                                self.write(&s.to_string());
24959                            }
24960                            self.write(")");
24961                        }
24962                    }
24963                }
24964            }
24965            DataType::CharacterSet { name } => {
24966                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24967                self.write_keyword("CHAR CHARACTER SET ");
24968                self.write(name);
24969            }
24970            _ => self.write("UNKNOWN"),
24971        }
24972        Ok(())
24973    }
24974
24975    // === Helper methods ===
24976
24977    #[inline]
24978    fn write(&mut self, s: &str) {
24979        self.output.push_str(s);
24980    }
24981
24982    #[inline]
24983    fn write_space(&mut self) {
24984        self.output.push(' ');
24985    }
24986
24987    #[inline]
24988    fn write_keyword(&mut self, keyword: &str) {
24989        if self.config.uppercase_keywords {
24990            self.output.push_str(keyword);
24991        } else {
24992            for b in keyword.bytes() {
24993                self.output.push(b.to_ascii_lowercase() as char);
24994            }
24995        }
24996    }
24997
24998    /// Write a function name respecting the normalize_functions config setting
24999    fn write_func_name(&mut self, name: &str) {
25000        let normalized = self.normalize_func_name(name);
25001        self.output.push_str(normalized.as_ref());
25002    }
25003
25004    /// Convert strptime format string to Exasol format string
25005    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25006    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25007    fn convert_strptime_to_exasol_format(format: &str) -> String {
25008        let mut result = String::new();
25009        let chars: Vec<char> = format.chars().collect();
25010        let mut i = 0;
25011        while i < chars.len() {
25012            if chars[i] == '%' && i + 1 < chars.len() {
25013                let spec = chars[i + 1];
25014                let exasol_spec = match spec {
25015                    'Y' => "YYYY",
25016                    'y' => "YY",
25017                    'm' => "MM",
25018                    'd' => "DD",
25019                    'H' => "HH",
25020                    'M' => "MI",
25021                    'S' => "SS",
25022                    'a' => "DY",    // abbreviated weekday name
25023                    'A' => "DAY",   // full weekday name
25024                    'b' => "MON",   // abbreviated month name
25025                    'B' => "MONTH", // full month name
25026                    'I' => "H12",   // 12-hour format
25027                    'u' => "ID",    // ISO weekday (1-7)
25028                    'V' => "IW",    // ISO week number
25029                    'G' => "IYYY",  // ISO year
25030                    'W' => "UW",    // Week number (Monday as first day)
25031                    'U' => "UW",    // Week number (Sunday as first day)
25032                    'z' => "Z",     // timezone offset
25033                    _ => {
25034                        // Unknown specifier, keep as-is
25035                        result.push('%');
25036                        result.push(spec);
25037                        i += 2;
25038                        continue;
25039                    }
25040                };
25041                result.push_str(exasol_spec);
25042                i += 2;
25043            } else {
25044                result.push(chars[i]);
25045                i += 1;
25046            }
25047        }
25048        result
25049    }
25050
25051    /// Convert strptime format string to PostgreSQL/Redshift format string
25052    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25053    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25054    fn convert_strptime_to_postgres_format(format: &str) -> String {
25055        let mut result = String::new();
25056        let chars: Vec<char> = format.chars().collect();
25057        let mut i = 0;
25058        while i < chars.len() {
25059            if chars[i] == '%' && i + 1 < chars.len() {
25060                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25061                if chars[i + 1] == '-' && i + 2 < chars.len() {
25062                    let spec = chars[i + 2];
25063                    let pg_spec = match spec {
25064                        'd' => "FMDD",
25065                        'm' => "FMMM",
25066                        'H' => "FMHH24",
25067                        'M' => "FMMI",
25068                        'S' => "FMSS",
25069                        _ => {
25070                            result.push('%');
25071                            result.push('-');
25072                            result.push(spec);
25073                            i += 3;
25074                            continue;
25075                        }
25076                    };
25077                    result.push_str(pg_spec);
25078                    i += 3;
25079                    continue;
25080                }
25081                let spec = chars[i + 1];
25082                let pg_spec = match spec {
25083                    'Y' => "YYYY",
25084                    'y' => "YY",
25085                    'm' => "MM",
25086                    'd' => "DD",
25087                    'H' => "HH24",
25088                    'I' => "HH12",
25089                    'M' => "MI",
25090                    'S' => "SS",
25091                    'f' => "US",      // microseconds
25092                    'u' => "D",       // day of week (1=Monday)
25093                    'j' => "DDD",     // day of year
25094                    'z' => "OF",      // UTC offset
25095                    'Z' => "TZ",      // timezone name
25096                    'A' => "TMDay",   // full weekday name
25097                    'a' => "TMDy",    // abbreviated weekday name
25098                    'b' => "TMMon",   // abbreviated month name
25099                    'B' => "TMMonth", // full month name
25100                    'U' => "WW",      // week number
25101                    _ => {
25102                        // Unknown specifier, keep as-is
25103                        result.push('%');
25104                        result.push(spec);
25105                        i += 2;
25106                        continue;
25107                    }
25108                };
25109                result.push_str(pg_spec);
25110                i += 2;
25111            } else {
25112                result.push(chars[i]);
25113                i += 1;
25114            }
25115        }
25116        result
25117    }
25118
25119    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25120    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25121        if self.config.limit_only_literals {
25122            if let Some(value) = Self::try_evaluate_constant(expr) {
25123                self.write(&value.to_string());
25124                return Ok(());
25125            }
25126        }
25127        self.generate_expression(expr)
25128    }
25129
25130    /// Format a comment with proper spacing.
25131    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25132    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25133    fn write_formatted_comment(&mut self, comment: &str) {
25134        // Normalize all comments to block comment format /* ... */
25135        // This matches Python sqlglot behavior which always outputs block comments
25136        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25137            // Already block comment - extract inner content
25138            // Preserve internal whitespace, but ensure at least one space padding
25139            &comment[2..comment.len() - 2]
25140        } else if comment.starts_with("--") {
25141            // Line comment - extract content after --
25142            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25143            &comment[2..]
25144        } else {
25145            // Raw content (no delimiters)
25146            comment
25147        };
25148        // Skip empty comments (e.g., bare "--" with no content)
25149        if content.trim().is_empty() {
25150            return;
25151        }
25152        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25153        // This matches Python sqlglot's sanitize_comment behavior.
25154        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25155        let content = &sanitized;
25156        // Ensure at least one space after /* and before */
25157        self.output.push_str("/*");
25158        if !content.starts_with(' ') {
25159            self.output.push(' ');
25160        }
25161        self.output.push_str(content);
25162        if !content.ends_with(' ') {
25163            self.output.push(' ');
25164        }
25165        self.output.push_str("*/");
25166    }
25167
25168    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25169    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25170    fn escape_block_for_single_quote(&self, block: &str) -> String {
25171        let escape_backslash = matches!(
25172            self.config.dialect,
25173            Some(crate::dialects::DialectType::Snowflake)
25174        );
25175        let mut escaped = String::with_capacity(block.len() + 4);
25176        for ch in block.chars() {
25177            if ch == '\'' {
25178                escaped.push('\\');
25179                escaped.push('\'');
25180            } else if escape_backslash && ch == '\\' {
25181                escaped.push('\\');
25182                escaped.push('\\');
25183            } else {
25184                escaped.push(ch);
25185            }
25186        }
25187        escaped
25188    }
25189
25190    fn write_newline(&mut self) {
25191        self.output.push('\n');
25192    }
25193
25194    fn write_indent(&mut self) {
25195        for _ in 0..self.indent_level {
25196            self.output.push_str(self.config.indent);
25197        }
25198    }
25199
25200    // === SQLGlot-style pretty printing helpers ===
25201
25202    /// Returns the separator string for pretty printing.
25203    /// Check if the total length of arguments exceeds max_text_width.
25204    /// Used for dynamic line breaking in expressions() formatting.
25205    fn too_wide(&self, args: &[String]) -> bool {
25206        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25207    }
25208
25209    /// Generate an expression to a string using a temporary non-pretty generator.
25210    /// Useful for width calculations before deciding on formatting.
25211    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25212        let config = GeneratorConfig {
25213            pretty: false,
25214            dialect: self.config.dialect,
25215            ..Default::default()
25216        };
25217        let mut gen = Generator::with_config(config);
25218        gen.generate_expression(expr)?;
25219        Ok(gen.output)
25220    }
25221
25222    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25223    /// In pretty mode: newline + indented keyword + newline + indented condition
25224    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25225        if self.config.pretty {
25226            self.write_newline();
25227            self.write_indent();
25228            self.write_keyword(keyword);
25229            self.write_newline();
25230            self.indent_level += 1;
25231            self.write_indent();
25232            self.generate_expression(condition)?;
25233            self.indent_level -= 1;
25234        } else {
25235            self.write_space();
25236            self.write_keyword(keyword);
25237            self.write_space();
25238            self.generate_expression(condition)?;
25239        }
25240        Ok(())
25241    }
25242
25243    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25244    /// In pretty mode: each expression on new line with indentation
25245    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25246        if exprs.is_empty() {
25247            return Ok(());
25248        }
25249
25250        if self.config.pretty {
25251            self.write_newline();
25252            self.write_indent();
25253            self.write_keyword(keyword);
25254            self.write_newline();
25255            self.indent_level += 1;
25256            for (i, expr) in exprs.iter().enumerate() {
25257                if i > 0 {
25258                    self.write(",");
25259                    self.write_newline();
25260                }
25261                self.write_indent();
25262                self.generate_expression(expr)?;
25263            }
25264            self.indent_level -= 1;
25265        } else {
25266            self.write_space();
25267            self.write_keyword(keyword);
25268            self.write_space();
25269            for (i, expr) in exprs.iter().enumerate() {
25270                if i > 0 {
25271                    self.write(", ");
25272                }
25273                self.generate_expression(expr)?;
25274            }
25275        }
25276        Ok(())
25277    }
25278
25279    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25280    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25281        if orderings.is_empty() {
25282            return Ok(());
25283        }
25284
25285        if self.config.pretty {
25286            self.write_newline();
25287            self.write_indent();
25288            self.write_keyword(keyword);
25289            self.write_newline();
25290            self.indent_level += 1;
25291            for (i, ordered) in orderings.iter().enumerate() {
25292                if i > 0 {
25293                    self.write(",");
25294                    self.write_newline();
25295                }
25296                self.write_indent();
25297                self.generate_ordered(ordered)?;
25298            }
25299            self.indent_level -= 1;
25300        } else {
25301            self.write_space();
25302            self.write_keyword(keyword);
25303            self.write_space();
25304            for (i, ordered) in orderings.iter().enumerate() {
25305                if i > 0 {
25306                    self.write(", ");
25307                }
25308                self.generate_ordered(ordered)?;
25309            }
25310        }
25311        Ok(())
25312    }
25313
25314    /// Writes WINDOW clause with named window definitions
25315    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25316        if windows.is_empty() {
25317            return Ok(());
25318        }
25319
25320        if self.config.pretty {
25321            self.write_newline();
25322            self.write_indent();
25323            self.write_keyword("WINDOW");
25324            self.write_newline();
25325            self.indent_level += 1;
25326            for (i, named_window) in windows.iter().enumerate() {
25327                if i > 0 {
25328                    self.write(",");
25329                    self.write_newline();
25330                }
25331                self.write_indent();
25332                self.generate_identifier(&named_window.name)?;
25333                self.write_space();
25334                self.write_keyword("AS");
25335                self.write(" (");
25336                self.generate_over(&named_window.spec)?;
25337                self.write(")");
25338            }
25339            self.indent_level -= 1;
25340        } else {
25341            self.write_space();
25342            self.write_keyword("WINDOW");
25343            self.write_space();
25344            for (i, named_window) in windows.iter().enumerate() {
25345                if i > 0 {
25346                    self.write(", ");
25347                }
25348                self.generate_identifier(&named_window.name)?;
25349                self.write_space();
25350                self.write_keyword("AS");
25351                self.write(" (");
25352                self.generate_over(&named_window.spec)?;
25353                self.write(")");
25354            }
25355        }
25356        Ok(())
25357    }
25358
25359    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25360    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25361        // AI_AGG(this, expression)
25362        self.write_keyword("AI_AGG");
25363        self.write("(");
25364        self.generate_expression(&e.this)?;
25365        self.write(", ");
25366        self.generate_expression(&e.expression)?;
25367        self.write(")");
25368        Ok(())
25369    }
25370
25371    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25372        // AI_CLASSIFY(input, [categories], [config])
25373        self.write_keyword("AI_CLASSIFY");
25374        self.write("(");
25375        self.generate_expression(&e.this)?;
25376        if let Some(categories) = &e.categories {
25377            self.write(", ");
25378            self.generate_expression(categories)?;
25379        }
25380        if let Some(config) = &e.config {
25381            self.write(", ");
25382            self.generate_expression(config)?;
25383        }
25384        self.write(")");
25385        Ok(())
25386    }
25387
25388    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25389        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25390        self.write_keyword("ADD");
25391        self.write_space();
25392        if e.exists {
25393            self.write_keyword("IF NOT EXISTS");
25394            self.write_space();
25395        }
25396        self.generate_expression(&e.this)?;
25397        if let Some(location) = &e.location {
25398            self.write_space();
25399            self.generate_expression(location)?;
25400        }
25401        Ok(())
25402    }
25403
25404    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25405        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25406        self.write_keyword("ALGORITHM");
25407        self.write("=");
25408        self.generate_expression(&e.this)?;
25409        Ok(())
25410    }
25411
25412    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25413        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25414        self.generate_expression(&e.this)?;
25415        self.write_space();
25416        self.write_keyword("AS");
25417        self.write(" (");
25418        for (i, expr) in e.expressions.iter().enumerate() {
25419            if i > 0 {
25420                self.write(", ");
25421            }
25422            self.generate_expression(expr)?;
25423        }
25424        self.write(")");
25425        Ok(())
25426    }
25427
25428    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25429        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25430        self.write_keyword("ALLOWED_VALUES");
25431        self.write_space();
25432        for (i, expr) in e.expressions.iter().enumerate() {
25433            if i > 0 {
25434                self.write(", ");
25435            }
25436            self.generate_expression(expr)?;
25437        }
25438        Ok(())
25439    }
25440
25441    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25442        // Python: complex logic based on dtype, default, comment, visible, etc.
25443        self.write_keyword("ALTER COLUMN");
25444        self.write_space();
25445        self.generate_expression(&e.this)?;
25446
25447        if let Some(dtype) = &e.dtype {
25448            self.write_space();
25449            self.write_keyword("SET DATA TYPE");
25450            self.write_space();
25451            self.generate_expression(dtype)?;
25452            if let Some(collate) = &e.collate {
25453                self.write_space();
25454                self.write_keyword("COLLATE");
25455                self.write_space();
25456                self.generate_expression(collate)?;
25457            }
25458            if let Some(using) = &e.using {
25459                self.write_space();
25460                self.write_keyword("USING");
25461                self.write_space();
25462                self.generate_expression(using)?;
25463            }
25464        } else if let Some(default) = &e.default {
25465            self.write_space();
25466            self.write_keyword("SET DEFAULT");
25467            self.write_space();
25468            self.generate_expression(default)?;
25469        } else if let Some(comment) = &e.comment {
25470            self.write_space();
25471            self.write_keyword("COMMENT");
25472            self.write_space();
25473            self.generate_expression(comment)?;
25474        } else if let Some(drop) = &e.drop {
25475            self.write_space();
25476            self.write_keyword("DROP");
25477            self.write_space();
25478            self.generate_expression(drop)?;
25479        } else if let Some(visible) = &e.visible {
25480            self.write_space();
25481            self.generate_expression(visible)?;
25482        } else if let Some(rename_to) = &e.rename_to {
25483            self.write_space();
25484            self.write_keyword("RENAME TO");
25485            self.write_space();
25486            self.generate_expression(rename_to)?;
25487        } else if let Some(allow_null) = &e.allow_null {
25488            self.write_space();
25489            self.generate_expression(allow_null)?;
25490        }
25491        Ok(())
25492    }
25493
25494    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
25495        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
25496        self.write_keyword("ALTER SESSION");
25497        self.write_space();
25498        if e.unset.is_some() {
25499            self.write_keyword("UNSET");
25500        } else {
25501            self.write_keyword("SET");
25502        }
25503        self.write_space();
25504        for (i, expr) in e.expressions.iter().enumerate() {
25505            if i > 0 {
25506                self.write(", ");
25507            }
25508            self.generate_expression(expr)?;
25509        }
25510        Ok(())
25511    }
25512
25513    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
25514        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
25515        self.write_keyword("SET");
25516
25517        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
25518        if let Some(opt) = &e.option {
25519            self.write_space();
25520            self.generate_expression(opt)?;
25521        }
25522
25523        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
25524        // Check if expressions look like property assignments
25525        if !e.expressions.is_empty() {
25526            // Check if this looks like property assignments (for SET PROPERTIES)
25527            let is_properties = e
25528                .expressions
25529                .iter()
25530                .any(|expr| matches!(expr, Expression::Eq(_)));
25531            if is_properties && e.option.is_none() {
25532                self.write_space();
25533                self.write_keyword("PROPERTIES");
25534            }
25535            self.write_space();
25536            for (i, expr) in e.expressions.iter().enumerate() {
25537                if i > 0 {
25538                    self.write(", ");
25539                }
25540                self.generate_expression(expr)?;
25541            }
25542        }
25543
25544        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
25545        if let Some(file_format) = &e.file_format {
25546            self.write(" ");
25547            self.write_keyword("STAGE_FILE_FORMAT");
25548            self.write(" = (");
25549            self.generate_space_separated_properties(file_format)?;
25550            self.write(")");
25551        }
25552
25553        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
25554        if let Some(copy_options) = &e.copy_options {
25555            self.write(" ");
25556            self.write_keyword("STAGE_COPY_OPTIONS");
25557            self.write(" = (");
25558            self.generate_space_separated_properties(copy_options)?;
25559            self.write(")");
25560        }
25561
25562        // Generate TAG ...
25563        if let Some(tag) = &e.tag {
25564            self.write(" ");
25565            self.write_keyword("TAG");
25566            self.write(" ");
25567            self.generate_expression(tag)?;
25568        }
25569
25570        Ok(())
25571    }
25572
25573    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25574    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25575        match expr {
25576            Expression::Tuple(t) => {
25577                for (i, prop) in t.expressions.iter().enumerate() {
25578                    if i > 0 {
25579                        self.write(" ");
25580                    }
25581                    self.generate_expression(prop)?;
25582                }
25583            }
25584            _ => {
25585                self.generate_expression(expr)?;
25586            }
25587        }
25588        Ok(())
25589    }
25590
25591    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25592        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25593        self.write_keyword("ALTER");
25594        if e.compound.is_some() {
25595            self.write_space();
25596            self.write_keyword("COMPOUND");
25597        }
25598        self.write_space();
25599        self.write_keyword("SORTKEY");
25600        self.write_space();
25601        if let Some(this) = &e.this {
25602            self.generate_expression(this)?;
25603        } else if !e.expressions.is_empty() {
25604            self.write("(");
25605            for (i, expr) in e.expressions.iter().enumerate() {
25606                if i > 0 {
25607                    self.write(", ");
25608                }
25609                self.generate_expression(expr)?;
25610            }
25611            self.write(")");
25612        }
25613        Ok(())
25614    }
25615
25616    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25617        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25618        self.write_keyword("ANALYZE");
25619        if !e.options.is_empty() {
25620            self.write_space();
25621            for (i, opt) in e.options.iter().enumerate() {
25622                if i > 0 {
25623                    self.write_space();
25624                }
25625                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25626                if let Expression::Identifier(id) = opt {
25627                    self.write_keyword(&id.name);
25628                } else {
25629                    self.generate_expression(opt)?;
25630                }
25631            }
25632        }
25633        if let Some(kind) = &e.kind {
25634            self.write_space();
25635            self.write_keyword(kind);
25636        }
25637        if let Some(this) = &e.this {
25638            self.write_space();
25639            self.generate_expression(this)?;
25640        }
25641        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25642        if !e.columns.is_empty() {
25643            self.write("(");
25644            for (i, col) in e.columns.iter().enumerate() {
25645                if i > 0 {
25646                    self.write(", ");
25647                }
25648                self.write(col);
25649            }
25650            self.write(")");
25651        }
25652        if let Some(partition) = &e.partition {
25653            self.write_space();
25654            self.generate_expression(partition)?;
25655        }
25656        if let Some(mode) = &e.mode {
25657            self.write_space();
25658            self.generate_expression(mode)?;
25659        }
25660        if let Some(expression) = &e.expression {
25661            self.write_space();
25662            self.generate_expression(expression)?;
25663        }
25664        if !e.properties.is_empty() {
25665            self.write_space();
25666            self.write_keyword(self.config.with_properties_prefix);
25667            self.write(" (");
25668            for (i, prop) in e.properties.iter().enumerate() {
25669                if i > 0 {
25670                    self.write(", ");
25671                }
25672                self.generate_expression(prop)?;
25673            }
25674            self.write(")");
25675        }
25676        Ok(())
25677    }
25678
25679    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25680        // Python: return f"DELETE{kind} STATISTICS"
25681        self.write_keyword("DELETE");
25682        if let Some(kind) = &e.kind {
25683            self.write_space();
25684            self.write_keyword(kind);
25685        }
25686        self.write_space();
25687        self.write_keyword("STATISTICS");
25688        Ok(())
25689    }
25690
25691    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25692        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25693        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25694        if let Expression::Identifier(id) = e.this.as_ref() {
25695            self.write_keyword(&id.name);
25696        } else {
25697            self.generate_expression(&e.this)?;
25698        }
25699        self.write_space();
25700        self.write_keyword("HISTOGRAM ON");
25701        self.write_space();
25702        for (i, expr) in e.expressions.iter().enumerate() {
25703            if i > 0 {
25704                self.write(", ");
25705            }
25706            self.generate_expression(expr)?;
25707        }
25708        if let Some(expression) = &e.expression {
25709            self.write_space();
25710            self.generate_expression(expression)?;
25711        }
25712        if let Some(update_options) = &e.update_options {
25713            self.write_space();
25714            self.generate_expression(update_options)?;
25715            self.write_space();
25716            self.write_keyword("UPDATE");
25717        }
25718        Ok(())
25719    }
25720
25721    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25722        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25723        self.write_keyword("LIST CHAINED ROWS");
25724        if let Some(expression) = &e.expression {
25725            self.write_space();
25726            self.write_keyword("INTO");
25727            self.write_space();
25728            self.generate_expression(expression)?;
25729        }
25730        Ok(())
25731    }
25732
25733    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25734        // Python: return f"SAMPLE {sample} {kind}"
25735        self.write_keyword("SAMPLE");
25736        self.write_space();
25737        if let Some(sample) = &e.sample {
25738            self.generate_expression(sample)?;
25739            self.write_space();
25740        }
25741        self.write_keyword(&e.kind);
25742        Ok(())
25743    }
25744
25745    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25746        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25747        self.write_keyword(&e.kind);
25748        if let Some(option) = &e.option {
25749            self.write_space();
25750            self.generate_expression(option)?;
25751        }
25752        self.write_space();
25753        self.write_keyword("STATISTICS");
25754        if let Some(this) = &e.this {
25755            self.write_space();
25756            self.generate_expression(this)?;
25757        }
25758        if !e.expressions.is_empty() {
25759            self.write_space();
25760            for (i, expr) in e.expressions.iter().enumerate() {
25761                if i > 0 {
25762                    self.write(", ");
25763                }
25764                self.generate_expression(expr)?;
25765            }
25766        }
25767        Ok(())
25768    }
25769
25770    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25771        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25772        self.write_keyword("VALIDATE");
25773        self.write_space();
25774        self.write_keyword(&e.kind);
25775        if let Some(this) = &e.this {
25776            self.write_space();
25777            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25778            if let Expression::Identifier(id) = this.as_ref() {
25779                self.write_keyword(&id.name);
25780            } else {
25781                self.generate_expression(this)?;
25782            }
25783        }
25784        if let Some(expression) = &e.expression {
25785            self.write_space();
25786            self.write_keyword("INTO");
25787            self.write_space();
25788            self.generate_expression(expression)?;
25789        }
25790        Ok(())
25791    }
25792
25793    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25794        // Python: return f"WITH {expressions}"
25795        self.write_keyword("WITH");
25796        self.write_space();
25797        for (i, expr) in e.expressions.iter().enumerate() {
25798            if i > 0 {
25799                self.write(", ");
25800            }
25801            self.generate_expression(expr)?;
25802        }
25803        Ok(())
25804    }
25805
25806    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25807        // Anonymous represents a generic function call: FUNC_NAME(args...)
25808        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25809        self.generate_expression(&e.this)?;
25810        self.write("(");
25811        for (i, arg) in e.expressions.iter().enumerate() {
25812            if i > 0 {
25813                self.write(", ");
25814            }
25815            self.generate_expression(arg)?;
25816        }
25817        self.write(")");
25818        Ok(())
25819    }
25820
25821    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25822        // Same as Anonymous but for aggregate functions
25823        self.generate_expression(&e.this)?;
25824        self.write("(");
25825        for (i, arg) in e.expressions.iter().enumerate() {
25826            if i > 0 {
25827                self.write(", ");
25828            }
25829            self.generate_expression(arg)?;
25830        }
25831        self.write(")");
25832        Ok(())
25833    }
25834
25835    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25836        // Python: return f"{this} APPLY({expr})"
25837        self.generate_expression(&e.this)?;
25838        self.write_space();
25839        self.write_keyword("APPLY");
25840        self.write("(");
25841        self.generate_expression(&e.expression)?;
25842        self.write(")");
25843        Ok(())
25844    }
25845
25846    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25847        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25848        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25849        self.write("(");
25850        self.generate_expression(&e.this)?;
25851        if let Some(percentile) = &e.percentile {
25852            self.write(", ");
25853            self.generate_expression(percentile)?;
25854        }
25855        self.write(")");
25856        Ok(())
25857    }
25858
25859    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25860        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25861        self.write_keyword("APPROX_QUANTILE");
25862        self.write("(");
25863        self.generate_expression(&e.this)?;
25864        if let Some(quantile) = &e.quantile {
25865            self.write(", ");
25866            self.generate_expression(quantile)?;
25867        }
25868        if let Some(accuracy) = &e.accuracy {
25869            self.write(", ");
25870            self.generate_expression(accuracy)?;
25871        }
25872        if let Some(weight) = &e.weight {
25873            self.write(", ");
25874            self.generate_expression(weight)?;
25875        }
25876        self.write(")");
25877        Ok(())
25878    }
25879
25880    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25881        // APPROX_QUANTILES(this, expression)
25882        self.write_keyword("APPROX_QUANTILES");
25883        self.write("(");
25884        self.generate_expression(&e.this)?;
25885        if let Some(expression) = &e.expression {
25886            self.write(", ");
25887            self.generate_expression(expression)?;
25888        }
25889        self.write(")");
25890        Ok(())
25891    }
25892
25893    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25894        // APPROX_TOP_K(this[, expression][, counters])
25895        self.write_keyword("APPROX_TOP_K");
25896        self.write("(");
25897        self.generate_expression(&e.this)?;
25898        if let Some(expression) = &e.expression {
25899            self.write(", ");
25900            self.generate_expression(expression)?;
25901        }
25902        if let Some(counters) = &e.counters {
25903            self.write(", ");
25904            self.generate_expression(counters)?;
25905        }
25906        self.write(")");
25907        Ok(())
25908    }
25909
25910    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25911        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25912        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25913        self.write("(");
25914        self.generate_expression(&e.this)?;
25915        if let Some(expression) = &e.expression {
25916            self.write(", ");
25917            self.generate_expression(expression)?;
25918        }
25919        self.write(")");
25920        Ok(())
25921    }
25922
25923    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25924        // APPROX_TOP_K_COMBINE(this[, expression])
25925        self.write_keyword("APPROX_TOP_K_COMBINE");
25926        self.write("(");
25927        self.generate_expression(&e.this)?;
25928        if let Some(expression) = &e.expression {
25929            self.write(", ");
25930            self.generate_expression(expression)?;
25931        }
25932        self.write(")");
25933        Ok(())
25934    }
25935
25936    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25937        // APPROX_TOP_K_ESTIMATE(this[, expression])
25938        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25939        self.write("(");
25940        self.generate_expression(&e.this)?;
25941        if let Some(expression) = &e.expression {
25942            self.write(", ");
25943            self.generate_expression(expression)?;
25944        }
25945        self.write(")");
25946        Ok(())
25947    }
25948
25949    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25950        // APPROX_TOP_SUM(this, expression[, count])
25951        self.write_keyword("APPROX_TOP_SUM");
25952        self.write("(");
25953        self.generate_expression(&e.this)?;
25954        self.write(", ");
25955        self.generate_expression(&e.expression)?;
25956        if let Some(count) = &e.count {
25957            self.write(", ");
25958            self.generate_expression(count)?;
25959        }
25960        self.write(")");
25961        Ok(())
25962    }
25963
25964    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25965        // ARG_MAX(this, expression[, count])
25966        self.write_keyword("ARG_MAX");
25967        self.write("(");
25968        self.generate_expression(&e.this)?;
25969        self.write(", ");
25970        self.generate_expression(&e.expression)?;
25971        if let Some(count) = &e.count {
25972            self.write(", ");
25973            self.generate_expression(count)?;
25974        }
25975        self.write(")");
25976        Ok(())
25977    }
25978
25979    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25980        // ARG_MIN(this, expression[, count])
25981        self.write_keyword("ARG_MIN");
25982        self.write("(");
25983        self.generate_expression(&e.this)?;
25984        self.write(", ");
25985        self.generate_expression(&e.expression)?;
25986        if let Some(count) = &e.count {
25987            self.write(", ");
25988            self.generate_expression(count)?;
25989        }
25990        self.write(")");
25991        Ok(())
25992    }
25993
25994    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25995        // ARRAY_ALL(this, expression)
25996        self.write_keyword("ARRAY_ALL");
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_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26006        // ARRAY_ANY(this, expression) - fallback implementation
26007        self.write_keyword("ARRAY_ANY");
26008        self.write("(");
26009        self.generate_expression(&e.this)?;
26010        self.write(", ");
26011        self.generate_expression(&e.expression)?;
26012        self.write(")");
26013        Ok(())
26014    }
26015
26016    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26017        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26018        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26019        self.write("(");
26020        for (i, expr) in e.expressions.iter().enumerate() {
26021            if i > 0 {
26022                self.write(", ");
26023            }
26024            self.generate_expression(expr)?;
26025        }
26026        self.write(")");
26027        Ok(())
26028    }
26029
26030    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26031        // ARRAY_SUM(this[, expression])
26032        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26033            self.write("arraySum");
26034        } else {
26035            self.write_keyword("ARRAY_SUM");
26036        }
26037        self.write("(");
26038        self.generate_expression(&e.this)?;
26039        if let Some(expression) = &e.expression {
26040            self.write(", ");
26041            self.generate_expression(expression)?;
26042        }
26043        self.write(")");
26044        Ok(())
26045    }
26046
26047    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26048        // Python: return f"{this} AT {index}"
26049        self.generate_expression(&e.this)?;
26050        self.write_space();
26051        self.write_keyword("AT");
26052        self.write_space();
26053        self.generate_expression(&e.expression)?;
26054        Ok(())
26055    }
26056
26057    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26058        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26059        self.write_keyword("ATTACH");
26060        if e.exists {
26061            self.write_space();
26062            self.write_keyword("IF NOT EXISTS");
26063        }
26064        self.write_space();
26065        self.generate_expression(&e.this)?;
26066        if !e.expressions.is_empty() {
26067            self.write(" (");
26068            for (i, expr) in e.expressions.iter().enumerate() {
26069                if i > 0 {
26070                    self.write(", ");
26071                }
26072                self.generate_expression(expr)?;
26073            }
26074            self.write(")");
26075        }
26076        Ok(())
26077    }
26078
26079    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26080        // AttachOption: this [expression]
26081        // Python sqlglot: no equals sign, just space-separated
26082        self.generate_expression(&e.this)?;
26083        if let Some(expression) = &e.expression {
26084            self.write_space();
26085            self.generate_expression(expression)?;
26086        }
26087        Ok(())
26088    }
26089
26090    /// Generate the auto_increment keyword and options for a column definition.
26091    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26092    /// GENERATED AS IDENTITY, etc.
26093    fn generate_auto_increment_keyword(
26094        &mut self,
26095        col: &crate::expressions::ColumnDef,
26096    ) -> Result<()> {
26097        use crate::dialects::DialectType;
26098        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26099            self.write_keyword("IDENTITY");
26100            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26101                self.write("(");
26102                if let Some(ref start) = col.auto_increment_start {
26103                    self.generate_expression(start)?;
26104                } else {
26105                    self.write("0");
26106                }
26107                self.write(", ");
26108                if let Some(ref inc) = col.auto_increment_increment {
26109                    self.generate_expression(inc)?;
26110                } else {
26111                    self.write("1");
26112                }
26113                self.write(")");
26114            }
26115        } else if matches!(
26116            self.config.dialect,
26117            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26118        ) {
26119            self.write_keyword("AUTOINCREMENT");
26120            if let Some(ref start) = col.auto_increment_start {
26121                self.write_space();
26122                self.write_keyword("START");
26123                self.write_space();
26124                self.generate_expression(start)?;
26125            }
26126            if let Some(ref inc) = col.auto_increment_increment {
26127                self.write_space();
26128                self.write_keyword("INCREMENT");
26129                self.write_space();
26130                self.generate_expression(inc)?;
26131            }
26132            if let Some(order) = col.auto_increment_order {
26133                self.write_space();
26134                if order {
26135                    self.write_keyword("ORDER");
26136                } else {
26137                    self.write_keyword("NOORDER");
26138                }
26139            }
26140        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26141            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26142            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26143                self.write(" (");
26144                let mut first = true;
26145                if let Some(ref start) = col.auto_increment_start {
26146                    self.write_keyword("START WITH");
26147                    self.write_space();
26148                    self.generate_expression(start)?;
26149                    first = false;
26150                }
26151                if let Some(ref inc) = col.auto_increment_increment {
26152                    if !first {
26153                        self.write_space();
26154                    }
26155                    self.write_keyword("INCREMENT BY");
26156                    self.write_space();
26157                    self.generate_expression(inc)?;
26158                }
26159                self.write(")");
26160            }
26161        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26162            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26163            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26164            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26165                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26166            } else {
26167                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26168            }
26169            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26170                self.write(" (");
26171                let mut first = true;
26172                if let Some(ref start) = col.auto_increment_start {
26173                    self.write_keyword("START WITH");
26174                    self.write_space();
26175                    self.generate_expression(start)?;
26176                    first = false;
26177                }
26178                if let Some(ref inc) = col.auto_increment_increment {
26179                    if !first {
26180                        self.write_space();
26181                    }
26182                    self.write_keyword("INCREMENT BY");
26183                    self.write_space();
26184                    self.generate_expression(inc)?;
26185                }
26186                self.write(")");
26187            }
26188        } else if matches!(
26189            self.config.dialect,
26190            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26191        ) {
26192            self.write_keyword("IDENTITY");
26193            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26194                self.write("(");
26195                if let Some(ref start) = col.auto_increment_start {
26196                    self.generate_expression(start)?;
26197                } else {
26198                    self.write("0");
26199                }
26200                self.write(", ");
26201                if let Some(ref inc) = col.auto_increment_increment {
26202                    self.generate_expression(inc)?;
26203                } else {
26204                    self.write("1");
26205                }
26206                self.write(")");
26207            }
26208        } else {
26209            self.write_keyword("AUTO_INCREMENT");
26210            if let Some(ref start) = col.auto_increment_start {
26211                self.write_space();
26212                self.write_keyword("START");
26213                self.write_space();
26214                self.generate_expression(start)?;
26215            }
26216            if let Some(ref inc) = col.auto_increment_increment {
26217                self.write_space();
26218                self.write_keyword("INCREMENT");
26219                self.write_space();
26220                self.generate_expression(inc)?;
26221            }
26222            if let Some(order) = col.auto_increment_order {
26223                self.write_space();
26224                if order {
26225                    self.write_keyword("ORDER");
26226                } else {
26227                    self.write_keyword("NOORDER");
26228                }
26229            }
26230        }
26231        Ok(())
26232    }
26233
26234    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26235        // AUTO_INCREMENT=value
26236        self.write_keyword("AUTO_INCREMENT");
26237        self.write("=");
26238        self.generate_expression(&e.this)?;
26239        Ok(())
26240    }
26241
26242    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26243        // AUTO_REFRESH=value
26244        self.write_keyword("AUTO_REFRESH");
26245        self.write("=");
26246        self.generate_expression(&e.this)?;
26247        Ok(())
26248    }
26249
26250    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26251        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26252        self.write_keyword("BACKUP");
26253        self.write_space();
26254        self.generate_expression(&e.this)?;
26255        Ok(())
26256    }
26257
26258    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26259        // BASE64_DECODE_BINARY(this[, alphabet])
26260        self.write_keyword("BASE64_DECODE_BINARY");
26261        self.write("(");
26262        self.generate_expression(&e.this)?;
26263        if let Some(alphabet) = &e.alphabet {
26264            self.write(", ");
26265            self.generate_expression(alphabet)?;
26266        }
26267        self.write(")");
26268        Ok(())
26269    }
26270
26271    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26272        // BASE64_DECODE_STRING(this[, alphabet])
26273        self.write_keyword("BASE64_DECODE_STRING");
26274        self.write("(");
26275        self.generate_expression(&e.this)?;
26276        if let Some(alphabet) = &e.alphabet {
26277            self.write(", ");
26278            self.generate_expression(alphabet)?;
26279        }
26280        self.write(")");
26281        Ok(())
26282    }
26283
26284    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26285        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26286        self.write_keyword("BASE64_ENCODE");
26287        self.write("(");
26288        self.generate_expression(&e.this)?;
26289        if let Some(max_line_length) = &e.max_line_length {
26290            self.write(", ");
26291            self.generate_expression(max_line_length)?;
26292        }
26293        if let Some(alphabet) = &e.alphabet {
26294            self.write(", ");
26295            self.generate_expression(alphabet)?;
26296        }
26297        self.write(")");
26298        Ok(())
26299    }
26300
26301    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26302        // BLOCKCOMPRESSION=... (complex Teradata property)
26303        self.write_keyword("BLOCKCOMPRESSION");
26304        self.write("=");
26305        if let Some(autotemp) = &e.autotemp {
26306            self.write_keyword("AUTOTEMP");
26307            self.write("(");
26308            self.generate_expression(autotemp)?;
26309            self.write(")");
26310        }
26311        if let Some(always) = &e.always {
26312            self.generate_expression(always)?;
26313        }
26314        if let Some(default) = &e.default {
26315            self.generate_expression(default)?;
26316        }
26317        if let Some(manual) = &e.manual {
26318            self.generate_expression(manual)?;
26319        }
26320        if let Some(never) = &e.never {
26321            self.generate_expression(never)?;
26322        }
26323        Ok(())
26324    }
26325
26326    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26327        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26328        self.write("((");
26329        self.generate_expression(&e.this)?;
26330        self.write(") ");
26331        self.write_keyword("AND");
26332        self.write(" (");
26333        self.generate_expression(&e.expression)?;
26334        self.write("))");
26335        Ok(())
26336    }
26337
26338    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26339        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26340        self.write("((");
26341        self.generate_expression(&e.this)?;
26342        self.write(") ");
26343        self.write_keyword("OR");
26344        self.write(" (");
26345        self.generate_expression(&e.expression)?;
26346        self.write("))");
26347        Ok(())
26348    }
26349
26350    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26351        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26352        self.write_keyword("BUILD");
26353        self.write_space();
26354        self.generate_expression(&e.this)?;
26355        Ok(())
26356    }
26357
26358    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26359        // Byte string literal like B'...' or X'...'
26360        self.generate_expression(&e.this)?;
26361        Ok(())
26362    }
26363
26364    fn generate_case_specific_column_constraint(
26365        &mut self,
26366        e: &CaseSpecificColumnConstraint,
26367    ) -> Result<()> {
26368        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26369        if e.not_.is_some() {
26370            self.write_keyword("NOT");
26371            self.write_space();
26372        }
26373        self.write_keyword("CASESPECIFIC");
26374        Ok(())
26375    }
26376
26377    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26378        // Cast to string type (dialect-specific)
26379        self.write_keyword("CAST");
26380        self.write("(");
26381        self.generate_expression(&e.this)?;
26382        if self.config.dialect == Some(DialectType::ClickHouse) {
26383            // ClickHouse: CAST(expr, 'type_string')
26384            self.write(", ");
26385        } else {
26386            self.write_space();
26387            self.write_keyword("AS");
26388            self.write_space();
26389        }
26390        if let Some(to) = &e.to {
26391            self.generate_expression(to)?;
26392        }
26393        self.write(")");
26394        Ok(())
26395    }
26396
26397    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26398        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26399        // Python: f"CHANGES ({information}){at_before}{end}"
26400        self.write_keyword("CHANGES");
26401        self.write(" (");
26402        if let Some(information) = &e.information {
26403            self.write_keyword("INFORMATION");
26404            self.write(" => ");
26405            self.generate_expression(information)?;
26406        }
26407        self.write(")");
26408        // at_before and end are HistoricalData expressions that generate their own keywords
26409        if let Some(at_before) = &e.at_before {
26410            self.write(" ");
26411            self.generate_expression(at_before)?;
26412        }
26413        if let Some(end) = &e.end {
26414            self.write(" ");
26415            self.generate_expression(end)?;
26416        }
26417        Ok(())
26418    }
26419
26420    fn generate_character_set_column_constraint(
26421        &mut self,
26422        e: &CharacterSetColumnConstraint,
26423    ) -> Result<()> {
26424        // CHARACTER SET charset_name
26425        self.write_keyword("CHARACTER SET");
26426        self.write_space();
26427        self.generate_expression(&e.this)?;
26428        Ok(())
26429    }
26430
26431    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26432        // [DEFAULT] CHARACTER SET=value
26433        if e.default.is_some() {
26434            self.write_keyword("DEFAULT");
26435            self.write_space();
26436        }
26437        self.write_keyword("CHARACTER SET");
26438        self.write("=");
26439        self.generate_expression(&e.this)?;
26440        Ok(())
26441    }
26442
26443    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26444        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26445        self.write_keyword("CHECK");
26446        self.write(" (");
26447        self.generate_expression(&e.this)?;
26448        self.write(")");
26449        if e.enforced.is_some() {
26450            self.write_space();
26451            self.write_keyword("ENFORCED");
26452        }
26453        Ok(())
26454    }
26455
26456    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26457        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26458        self.write_keyword("ASSUME");
26459        self.write(" (");
26460        self.generate_expression(&e.this)?;
26461        self.write(")");
26462        Ok(())
26463    }
26464
26465    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26466        // CHECK_JSON(this)
26467        self.write_keyword("CHECK_JSON");
26468        self.write("(");
26469        self.generate_expression(&e.this)?;
26470        self.write(")");
26471        Ok(())
26472    }
26473
26474    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
26475        // CHECK_XML(this)
26476        self.write_keyword("CHECK_XML");
26477        self.write("(");
26478        self.generate_expression(&e.this)?;
26479        self.write(")");
26480        Ok(())
26481    }
26482
26483    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
26484        // CHECKSUM=[ON|OFF|DEFAULT]
26485        self.write_keyword("CHECKSUM");
26486        self.write("=");
26487        if e.on.is_some() {
26488            self.write_keyword("ON");
26489        } else if e.default.is_some() {
26490            self.write_keyword("DEFAULT");
26491        } else {
26492            self.write_keyword("OFF");
26493        }
26494        Ok(())
26495    }
26496
26497    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
26498        // Python: return f"{shallow}{keyword} {this}"
26499        if e.shallow.is_some() {
26500            self.write_keyword("SHALLOW");
26501            self.write_space();
26502        }
26503        if e.copy.is_some() {
26504            self.write_keyword("COPY");
26505        } else {
26506            self.write_keyword("CLONE");
26507        }
26508        self.write_space();
26509        self.generate_expression(&e.this)?;
26510        Ok(())
26511    }
26512
26513    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
26514        // CLUSTER BY (expressions)
26515        self.write_keyword("CLUSTER BY");
26516        self.write(" (");
26517        for (i, ord) in e.expressions.iter().enumerate() {
26518            if i > 0 {
26519                self.write(", ");
26520            }
26521            self.generate_ordered(ord)?;
26522        }
26523        self.write(")");
26524        Ok(())
26525    }
26526
26527    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
26528        // BigQuery table property: CLUSTER BY col1, col2
26529        self.write_keyword("CLUSTER BY");
26530        self.write_space();
26531        for (i, col) in e.columns.iter().enumerate() {
26532            if i > 0 {
26533                self.write(", ");
26534            }
26535            self.generate_identifier(col)?;
26536        }
26537        Ok(())
26538    }
26539
26540    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
26541        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
26542        self.write_keyword("CLUSTERED BY");
26543        self.write(" (");
26544        for (i, expr) in e.expressions.iter().enumerate() {
26545            if i > 0 {
26546                self.write(", ");
26547            }
26548            self.generate_expression(expr)?;
26549        }
26550        self.write(")");
26551        if let Some(sorted_by) = &e.sorted_by {
26552            self.write_space();
26553            self.write_keyword("SORTED BY");
26554            self.write(" (");
26555            // Unwrap Tuple to avoid double parentheses
26556            if let Expression::Tuple(t) = sorted_by.as_ref() {
26557                for (i, expr) in t.expressions.iter().enumerate() {
26558                    if i > 0 {
26559                        self.write(", ");
26560                    }
26561                    self.generate_expression(expr)?;
26562                }
26563            } else {
26564                self.generate_expression(sorted_by)?;
26565            }
26566            self.write(")");
26567        }
26568        if let Some(buckets) = &e.buckets {
26569            self.write_space();
26570            self.write_keyword("INTO");
26571            self.write_space();
26572            self.generate_expression(buckets)?;
26573            self.write_space();
26574            self.write_keyword("BUCKETS");
26575        }
26576        Ok(())
26577    }
26578
26579    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26580        // [DEFAULT] COLLATE [=] value
26581        // BigQuery uses space: DEFAULT COLLATE 'en'
26582        // Others use equals: COLLATE='en'
26583        if e.default.is_some() {
26584            self.write_keyword("DEFAULT");
26585            self.write_space();
26586        }
26587        self.write_keyword("COLLATE");
26588        // BigQuery uses space between COLLATE and value
26589        match self.config.dialect {
26590            Some(DialectType::BigQuery) => self.write_space(),
26591            _ => self.write("="),
26592        }
26593        self.generate_expression(&e.this)?;
26594        Ok(())
26595    }
26596
26597    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26598        // ColumnConstraint is an enum
26599        match e {
26600            ColumnConstraint::NotNull => {
26601                self.write_keyword("NOT NULL");
26602            }
26603            ColumnConstraint::Null => {
26604                self.write_keyword("NULL");
26605            }
26606            ColumnConstraint::Unique => {
26607                self.write_keyword("UNIQUE");
26608            }
26609            ColumnConstraint::PrimaryKey => {
26610                self.write_keyword("PRIMARY KEY");
26611            }
26612            ColumnConstraint::Default(expr) => {
26613                self.write_keyword("DEFAULT");
26614                self.write_space();
26615                self.generate_expression(expr)?;
26616            }
26617            ColumnConstraint::Check(expr) => {
26618                self.write_keyword("CHECK");
26619                self.write(" (");
26620                self.generate_expression(expr)?;
26621                self.write(")");
26622            }
26623            ColumnConstraint::References(fk_ref) => {
26624                if fk_ref.has_foreign_key_keywords {
26625                    self.write_keyword("FOREIGN KEY");
26626                    self.write_space();
26627                }
26628                self.write_keyword("REFERENCES");
26629                self.write_space();
26630                self.generate_table(&fk_ref.table)?;
26631                if !fk_ref.columns.is_empty() {
26632                    self.write(" (");
26633                    for (i, col) in fk_ref.columns.iter().enumerate() {
26634                        if i > 0 {
26635                            self.write(", ");
26636                        }
26637                        self.generate_identifier(col)?;
26638                    }
26639                    self.write(")");
26640                }
26641            }
26642            ColumnConstraint::GeneratedAsIdentity(gen) => {
26643                self.write_keyword("GENERATED");
26644                self.write_space();
26645                if gen.always {
26646                    self.write_keyword("ALWAYS");
26647                } else {
26648                    self.write_keyword("BY DEFAULT");
26649                    if gen.on_null {
26650                        self.write_space();
26651                        self.write_keyword("ON NULL");
26652                    }
26653                }
26654                self.write_space();
26655                self.write_keyword("AS IDENTITY");
26656            }
26657            ColumnConstraint::Collate(collation) => {
26658                self.write_keyword("COLLATE");
26659                self.write_space();
26660                self.generate_identifier(collation)?;
26661            }
26662            ColumnConstraint::Comment(comment) => {
26663                self.write_keyword("COMMENT");
26664                self.write(" '");
26665                self.write(comment);
26666                self.write("'");
26667            }
26668            ColumnConstraint::ComputedColumn(cc) => {
26669                self.generate_computed_column_inline(cc)?;
26670            }
26671            ColumnConstraint::GeneratedAsRow(gar) => {
26672                self.generate_generated_as_row_inline(gar)?;
26673            }
26674            ColumnConstraint::Tags(tags) => {
26675                self.write_keyword("TAG");
26676                self.write(" (");
26677                for (i, expr) in tags.expressions.iter().enumerate() {
26678                    if i > 0 {
26679                        self.write(", ");
26680                    }
26681                    self.generate_expression(expr)?;
26682                }
26683                self.write(")");
26684            }
26685            ColumnConstraint::Path(path_expr) => {
26686                self.write_keyword("PATH");
26687                self.write_space();
26688                self.generate_expression(path_expr)?;
26689            }
26690        }
26691        Ok(())
26692    }
26693
26694    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26695        // ColumnPosition is an enum
26696        match e {
26697            ColumnPosition::First => {
26698                self.write_keyword("FIRST");
26699            }
26700            ColumnPosition::After(ident) => {
26701                self.write_keyword("AFTER");
26702                self.write_space();
26703                self.generate_identifier(ident)?;
26704            }
26705        }
26706        Ok(())
26707    }
26708
26709    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26710        // column(prefix)
26711        self.generate_expression(&e.this)?;
26712        self.write("(");
26713        self.generate_expression(&e.expression)?;
26714        self.write(")");
26715        Ok(())
26716    }
26717
26718    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26719        // If unpack is true, this came from * COLUMNS(pattern)
26720        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26721        if let Some(ref unpack) = e.unpack {
26722            if let Expression::Boolean(b) = unpack.as_ref() {
26723                if b.value {
26724                    self.write("*");
26725                }
26726            }
26727        }
26728        self.write_keyword("COLUMNS");
26729        self.write("(");
26730        self.generate_expression(&e.this)?;
26731        self.write(")");
26732        Ok(())
26733    }
26734
26735    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26736        // Combined aggregate: FUNC(args) combined
26737        self.generate_expression(&e.this)?;
26738        self.write("(");
26739        for (i, expr) in e.expressions.iter().enumerate() {
26740            if i > 0 {
26741                self.write(", ");
26742            }
26743            self.generate_expression(expr)?;
26744        }
26745        self.write(")");
26746        Ok(())
26747    }
26748
26749    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26750        // Combined parameterized aggregate: FUNC(params)(expressions)
26751        self.generate_expression(&e.this)?;
26752        self.write("(");
26753        for (i, param) in e.params.iter().enumerate() {
26754            if i > 0 {
26755                self.write(", ");
26756            }
26757            self.generate_expression(param)?;
26758        }
26759        self.write(")(");
26760        for (i, expr) in e.expressions.iter().enumerate() {
26761            if i > 0 {
26762                self.write(", ");
26763            }
26764            self.generate_expression(expr)?;
26765        }
26766        self.write(")");
26767        Ok(())
26768    }
26769
26770    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26771        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26772        self.write_keyword("COMMIT");
26773
26774        // TSQL always uses COMMIT TRANSACTION
26775        if e.this.is_none()
26776            && matches!(
26777                self.config.dialect,
26778                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26779            )
26780        {
26781            self.write_space();
26782            self.write_keyword("TRANSACTION");
26783        }
26784
26785        // Check if this has TRANSACTION keyword or transaction name
26786        if let Some(this) = &e.this {
26787            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26788            let is_transaction_marker = matches!(
26789                this.as_ref(),
26790                Expression::Identifier(id) if id.name == "TRANSACTION"
26791            );
26792
26793            self.write_space();
26794            self.write_keyword("TRANSACTION");
26795
26796            // If it's a real transaction name, output it
26797            if !is_transaction_marker {
26798                self.write_space();
26799                self.generate_expression(this)?;
26800            }
26801        }
26802
26803        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26804        if let Some(durability) = &e.durability {
26805            self.write_space();
26806            self.write_keyword("WITH");
26807            self.write(" (");
26808            self.write_keyword("DELAYED_DURABILITY");
26809            self.write(" = ");
26810            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26811                self.write_keyword("ON");
26812            } else {
26813                self.write_keyword("OFF");
26814            }
26815            self.write(")");
26816        }
26817
26818        // Output AND [NO] CHAIN
26819        if let Some(chain) = &e.chain {
26820            self.write_space();
26821            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26822                self.write_keyword("AND NO CHAIN");
26823            } else {
26824                self.write_keyword("AND CHAIN");
26825            }
26826        }
26827        Ok(())
26828    }
26829
26830    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26831        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26832        self.write("[");
26833        self.generate_expression(&e.this)?;
26834        self.write_space();
26835        self.write_keyword("FOR");
26836        self.write_space();
26837        self.generate_expression(&e.expression)?;
26838        // Handle optional position variable (for enumerate-like syntax)
26839        if let Some(pos) = &e.position {
26840            self.write(", ");
26841            self.generate_expression(pos)?;
26842        }
26843        if let Some(iterator) = &e.iterator {
26844            self.write_space();
26845            self.write_keyword("IN");
26846            self.write_space();
26847            self.generate_expression(iterator)?;
26848        }
26849        if let Some(condition) = &e.condition {
26850            self.write_space();
26851            self.write_keyword("IF");
26852            self.write_space();
26853            self.generate_expression(condition)?;
26854        }
26855        self.write("]");
26856        Ok(())
26857    }
26858
26859    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26860        // COMPRESS(this[, method])
26861        self.write_keyword("COMPRESS");
26862        self.write("(");
26863        self.generate_expression(&e.this)?;
26864        if let Some(method) = &e.method {
26865            self.write(", '");
26866            self.write(method);
26867            self.write("'");
26868        }
26869        self.write(")");
26870        Ok(())
26871    }
26872
26873    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26874        // Python: return f"COMPRESS {this}"
26875        self.write_keyword("COMPRESS");
26876        if let Some(this) = &e.this {
26877            self.write_space();
26878            self.generate_expression(this)?;
26879        }
26880        Ok(())
26881    }
26882
26883    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26884        // Python: return f"AS {this}{persisted}"
26885        self.write_keyword("AS");
26886        self.write_space();
26887        self.generate_expression(&e.this)?;
26888        if e.not_null.is_some() {
26889            self.write_space();
26890            self.write_keyword("PERSISTED NOT NULL");
26891        } else if e.persisted.is_some() {
26892            self.write_space();
26893            self.write_keyword("PERSISTED");
26894        }
26895        Ok(())
26896    }
26897
26898    /// Generate a ComputedColumn constraint inline within a column definition.
26899    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26900    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26901    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26902        let computed_expr = if matches!(
26903            self.config.dialect,
26904            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26905        ) {
26906            match &*cc.expression {
26907                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26908                {
26909                    let wrapped = Expression::Cast(Box::new(Cast {
26910                        this: y.this.clone(),
26911                        to: DataType::Date,
26912                        trailing_comments: Vec::new(),
26913                        double_colon_syntax: false,
26914                        format: None,
26915                        default: None,
26916                        inferred_type: None,
26917                    }));
26918                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26919                }
26920                Expression::Function(f)
26921                    if f.name.eq_ignore_ascii_case("YEAR")
26922                        && f.args.len() == 1
26923                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26924                {
26925                    let wrapped = Expression::Cast(Box::new(Cast {
26926                        this: f.args[0].clone(),
26927                        to: DataType::Date,
26928                        trailing_comments: Vec::new(),
26929                        double_colon_syntax: false,
26930                        format: None,
26931                        default: None,
26932                        inferred_type: None,
26933                    }));
26934                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26935                }
26936                _ => *cc.expression.clone(),
26937            }
26938        } else {
26939            *cc.expression.clone()
26940        };
26941
26942        match cc.persistence_kind.as_deref() {
26943            Some("STORED") | Some("VIRTUAL") => {
26944                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26945                self.write_keyword("GENERATED ALWAYS AS");
26946                self.write(" (");
26947                self.generate_expression(&computed_expr)?;
26948                self.write(")");
26949                self.write_space();
26950                if cc.persisted {
26951                    self.write_keyword("STORED");
26952                } else {
26953                    self.write_keyword("VIRTUAL");
26954                }
26955            }
26956            Some("PERSISTED") => {
26957                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26958                self.write_keyword("AS");
26959                self.write(" (");
26960                self.generate_expression(&computed_expr)?;
26961                self.write(")");
26962                self.write_space();
26963                self.write_keyword("PERSISTED");
26964                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26965                if let Some(ref dt) = cc.data_type {
26966                    self.write_space();
26967                    self.generate_data_type(dt)?;
26968                }
26969                if cc.not_null {
26970                    self.write_space();
26971                    self.write_keyword("NOT NULL");
26972                }
26973            }
26974            _ => {
26975                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26976                // TSQL computed column without PERSISTED: AS (expr)
26977                if matches!(
26978                    self.config.dialect,
26979                    Some(DialectType::Spark)
26980                        | Some(DialectType::Databricks)
26981                        | Some(DialectType::Hive)
26982                ) {
26983                    self.write_keyword("GENERATED ALWAYS AS");
26984                    self.write(" (");
26985                    self.generate_expression(&computed_expr)?;
26986                    self.write(")");
26987                } else if matches!(
26988                    self.config.dialect,
26989                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26990                ) {
26991                    self.write_keyword("AS");
26992                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26993                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26994                    if omit_parens {
26995                        self.write_space();
26996                        self.generate_expression(&computed_expr)?;
26997                    } else {
26998                        self.write(" (");
26999                        self.generate_expression(&computed_expr)?;
27000                        self.write(")");
27001                    }
27002                } else {
27003                    self.write_keyword("AS");
27004                    self.write(" (");
27005                    self.generate_expression(&computed_expr)?;
27006                    self.write(")");
27007                }
27008            }
27009        }
27010        Ok(())
27011    }
27012
27013    /// Generate a GeneratedAsRow constraint inline within a column definition.
27014    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27015    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27016        self.write_keyword("GENERATED ALWAYS AS ROW ");
27017        if gar.start {
27018            self.write_keyword("START");
27019        } else {
27020            self.write_keyword("END");
27021        }
27022        if gar.hidden {
27023            self.write_space();
27024            self.write_keyword("HIDDEN");
27025        }
27026        Ok(())
27027    }
27028
27029    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27030    fn generate_system_versioning_content(
27031        &mut self,
27032        e: &WithSystemVersioningProperty,
27033    ) -> Result<()> {
27034        let mut parts = Vec::new();
27035
27036        if let Some(this) = &e.this {
27037            let mut s = String::from("HISTORY_TABLE=");
27038            let mut gen = Generator::with_arc_config(self.config.clone());
27039            gen.generate_expression(this)?;
27040            s.push_str(&gen.output);
27041            parts.push(s);
27042        }
27043
27044        if let Some(data_consistency) = &e.data_consistency {
27045            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27046            let mut gen = Generator::with_arc_config(self.config.clone());
27047            gen.generate_expression(data_consistency)?;
27048            s.push_str(&gen.output);
27049            parts.push(s);
27050        }
27051
27052        if let Some(retention_period) = &e.retention_period {
27053            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27054            let mut gen = Generator::with_arc_config(self.config.clone());
27055            gen.generate_expression(retention_period)?;
27056            s.push_str(&gen.output);
27057            parts.push(s);
27058        }
27059
27060        self.write_keyword("SYSTEM_VERSIONING");
27061        self.write("=");
27062
27063        if !parts.is_empty() {
27064            self.write_keyword("ON");
27065            self.write("(");
27066            self.write(&parts.join(", "));
27067            self.write(")");
27068        } else if e.on.is_some() {
27069            self.write_keyword("ON");
27070        } else {
27071            self.write_keyword("OFF");
27072        }
27073
27074        Ok(())
27075    }
27076
27077    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27078        // Conditional INSERT for multi-table inserts
27079        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27080        if e.else_.is_some() {
27081            self.write_keyword("ELSE");
27082            self.write_space();
27083        } else if let Some(expression) = &e.expression {
27084            self.write_keyword("WHEN");
27085            self.write_space();
27086            self.generate_expression(expression)?;
27087            self.write_space();
27088            self.write_keyword("THEN");
27089            self.write_space();
27090        }
27091
27092        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27093        // without the "INSERT " prefix
27094        if let Expression::Insert(insert) = e.this.as_ref() {
27095            self.write_keyword("INTO");
27096            self.write_space();
27097            self.generate_table(&insert.table)?;
27098
27099            // Optional column list
27100            if !insert.columns.is_empty() {
27101                self.write(" (");
27102                for (i, col) in insert.columns.iter().enumerate() {
27103                    if i > 0 {
27104                        self.write(", ");
27105                    }
27106                    self.generate_identifier(col)?;
27107                }
27108                self.write(")");
27109            }
27110
27111            // Optional VALUES clause
27112            if !insert.values.is_empty() {
27113                self.write_space();
27114                self.write_keyword("VALUES");
27115                for (row_idx, row) in insert.values.iter().enumerate() {
27116                    if row_idx > 0 {
27117                        self.write(", ");
27118                    }
27119                    self.write(" (");
27120                    for (i, val) in row.iter().enumerate() {
27121                        if i > 0 {
27122                            self.write(", ");
27123                        }
27124                        self.generate_expression(val)?;
27125                    }
27126                    self.write(")");
27127                }
27128            }
27129        } else {
27130            // Fallback for non-Insert expressions
27131            self.generate_expression(&e.this)?;
27132        }
27133        Ok(())
27134    }
27135
27136    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27137        // Python: return f"CONSTRAINT {this} {expressions}"
27138        self.write_keyword("CONSTRAINT");
27139        self.write_space();
27140        self.generate_expression(&e.this)?;
27141        if !e.expressions.is_empty() {
27142            self.write_space();
27143            for (i, expr) in e.expressions.iter().enumerate() {
27144                if i > 0 {
27145                    self.write_space();
27146                }
27147                self.generate_expression(expr)?;
27148            }
27149        }
27150        Ok(())
27151    }
27152
27153    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27154        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27155        self.write_keyword("CONVERT_TIMEZONE");
27156        self.write("(");
27157        let mut first = true;
27158        if let Some(source_tz) = &e.source_tz {
27159            self.generate_expression(source_tz)?;
27160            first = false;
27161        }
27162        if let Some(target_tz) = &e.target_tz {
27163            if !first {
27164                self.write(", ");
27165            }
27166            self.generate_expression(target_tz)?;
27167            first = false;
27168        }
27169        if let Some(timestamp) = &e.timestamp {
27170            if !first {
27171                self.write(", ");
27172            }
27173            self.generate_expression(timestamp)?;
27174        }
27175        self.write(")");
27176        Ok(())
27177    }
27178
27179    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27180        // CONVERT(this USING dest)
27181        self.write_keyword("CONVERT");
27182        self.write("(");
27183        self.generate_expression(&e.this)?;
27184        if let Some(dest) = &e.dest {
27185            self.write_space();
27186            self.write_keyword("USING");
27187            self.write_space();
27188            self.generate_expression(dest)?;
27189        }
27190        self.write(")");
27191        Ok(())
27192    }
27193
27194    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27195        self.write_keyword("COPY");
27196        if e.is_into {
27197            self.write_space();
27198            self.write_keyword("INTO");
27199        }
27200        self.write_space();
27201
27202        // Generate target table or query (or stage for COPY INTO @stage)
27203        if let Expression::Literal(lit) = &e.this {
27204            if let Literal::String(s) = lit.as_ref() {
27205                if s.starts_with('@') {
27206                    self.write(s);
27207                } else {
27208                    self.generate_expression(&e.this)?;
27209                }
27210            }
27211        } else {
27212            self.generate_expression(&e.this)?;
27213        }
27214
27215        // FROM or TO based on kind
27216        if e.kind {
27217            // kind=true means FROM (loading into table)
27218            if self.config.pretty {
27219                self.write_newline();
27220            } else {
27221                self.write_space();
27222            }
27223            self.write_keyword("FROM");
27224            self.write_space();
27225        } else if !e.files.is_empty() {
27226            // kind=false means TO (exporting)
27227            if self.config.pretty {
27228                self.write_newline();
27229            } else {
27230                self.write_space();
27231            }
27232            self.write_keyword("TO");
27233            self.write_space();
27234        }
27235
27236        // Generate source/destination files
27237        for (i, file) in e.files.iter().enumerate() {
27238            if i > 0 {
27239                self.write_space();
27240            }
27241            // For stage references (strings starting with @), output without quotes
27242            if let Expression::Literal(lit) = file {
27243                if let Literal::String(s) = lit.as_ref() {
27244                    if s.starts_with('@') {
27245                        self.write(s);
27246                    } else {
27247                        self.generate_expression(file)?;
27248                    }
27249                }
27250            } else if let Expression::Identifier(id) = file {
27251                // Backtick-quoted file path (Databricks style: `s3://link`)
27252                if id.quoted {
27253                    self.write("`");
27254                    self.write(&id.name);
27255                    self.write("`");
27256                } else {
27257                    self.generate_expression(file)?;
27258                }
27259            } else {
27260                self.generate_expression(file)?;
27261            }
27262        }
27263
27264        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27265        if !e.with_wrapped {
27266            if let Some(ref creds) = e.credentials {
27267                if let Some(ref storage) = creds.storage {
27268                    if self.config.pretty {
27269                        self.write_newline();
27270                    } else {
27271                        self.write_space();
27272                    }
27273                    self.write_keyword("STORAGE_INTEGRATION");
27274                    self.write(" = ");
27275                    self.write(storage);
27276                }
27277                if creds.credentials.is_empty() {
27278                    // Empty credentials: CREDENTIALS = ()
27279                    if self.config.pretty {
27280                        self.write_newline();
27281                    } else {
27282                        self.write_space();
27283                    }
27284                    self.write_keyword("CREDENTIALS");
27285                    self.write(" = ()");
27286                } else {
27287                    if self.config.pretty {
27288                        self.write_newline();
27289                    } else {
27290                        self.write_space();
27291                    }
27292                    self.write_keyword("CREDENTIALS");
27293                    // Check if this is Redshift-style (single value with empty key)
27294                    // vs Snowflake-style (multiple key=value pairs)
27295                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27296                        // Redshift style: CREDENTIALS 'value'
27297                        self.write(" '");
27298                        self.write(&creds.credentials[0].1);
27299                        self.write("'");
27300                    } else {
27301                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27302                        self.write(" = (");
27303                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27304                            if i > 0 {
27305                                self.write_space();
27306                            }
27307                            self.write(k);
27308                            self.write("='");
27309                            self.write(v);
27310                            self.write("'");
27311                        }
27312                        self.write(")");
27313                    }
27314                }
27315                if let Some(ref encryption) = creds.encryption {
27316                    self.write_space();
27317                    self.write_keyword("ENCRYPTION");
27318                    self.write(" = ");
27319                    self.write(encryption);
27320                }
27321            }
27322        }
27323
27324        // Generate parameters
27325        if !e.params.is_empty() {
27326            if e.with_wrapped {
27327                // DuckDB/PostgreSQL/TSQL WITH (...) format
27328                self.write_space();
27329                self.write_keyword("WITH");
27330                self.write(" (");
27331                for (i, param) in e.params.iter().enumerate() {
27332                    if i > 0 {
27333                        self.write(", ");
27334                    }
27335                    self.generate_copy_param_with_format(param)?;
27336                }
27337                self.write(")");
27338            } else {
27339                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27340                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27341                // For Snowflake: KEY = VALUE
27342                for param in &e.params {
27343                    if self.config.pretty {
27344                        self.write_newline();
27345                    } else {
27346                        self.write_space();
27347                    }
27348                    // Preserve original case of parameter name (important for Redshift COPY options)
27349                    self.write(&param.name);
27350                    if let Some(ref value) = param.value {
27351                        // Use = only if it was present in the original (param.eq)
27352                        if param.eq {
27353                            self.write(" = ");
27354                        } else {
27355                            self.write(" ");
27356                        }
27357                        if !param.values.is_empty() {
27358                            self.write("(");
27359                            for (i, v) in param.values.iter().enumerate() {
27360                                if i > 0 {
27361                                    self.write_space();
27362                                }
27363                                self.generate_copy_nested_param(v)?;
27364                            }
27365                            self.write(")");
27366                        } else {
27367                            // For COPY parameter values, output identifiers without quoting
27368                            self.generate_copy_param_value(value)?;
27369                        }
27370                    } else if !param.values.is_empty() {
27371                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27372                        if param.eq {
27373                            self.write(" = (");
27374                        } else {
27375                            self.write(" (");
27376                        }
27377                        // Determine separator for values inside parentheses:
27378                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27379                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27380                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27381                        let is_key_value_pairs = param
27382                            .values
27383                            .first()
27384                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27385                        let sep = if is_key_value_pairs && param.eq {
27386                            " "
27387                        } else {
27388                            ", "
27389                        };
27390                        for (i, v) in param.values.iter().enumerate() {
27391                            if i > 0 {
27392                                self.write(sep);
27393                            }
27394                            self.generate_copy_nested_param(v)?;
27395                        }
27396                        self.write(")");
27397                    }
27398                }
27399            }
27400        }
27401
27402        Ok(())
27403    }
27404
27405    /// Generate a COPY parameter in WITH (...) format
27406    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27407    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27408        self.write_keyword(&param.name);
27409        if !param.values.is_empty() {
27410            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27411            self.write(" = (");
27412            for (i, v) in param.values.iter().enumerate() {
27413                if i > 0 {
27414                    self.write(", ");
27415                }
27416                self.generate_copy_nested_param(v)?;
27417            }
27418            self.write(")");
27419        } else if let Some(ref value) = param.value {
27420            if param.eq {
27421                self.write(" = ");
27422            } else {
27423                self.write(" ");
27424            }
27425            self.generate_expression(value)?;
27426        }
27427        Ok(())
27428    }
27429
27430    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27431    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27432        match expr {
27433            Expression::Eq(eq) => {
27434                // Generate key
27435                match &eq.left {
27436                    Expression::Column(c) => self.write(&c.name.name),
27437                    _ => self.generate_expression(&eq.left)?,
27438                }
27439                self.write("=");
27440                // Generate value
27441                match &eq.right {
27442                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27443                        let Literal::String(s) = lit.as_ref() else {
27444                            unreachable!()
27445                        };
27446                        self.write("'");
27447                        self.write(s);
27448                        self.write("'");
27449                    }
27450                    Expression::Tuple(t) => {
27451                        // For lists like NULL_IF=('', 'str1')
27452                        self.write("(");
27453                        if self.config.pretty {
27454                            self.write_newline();
27455                            self.indent_level += 1;
27456                            for (i, item) in t.expressions.iter().enumerate() {
27457                                if i > 0 {
27458                                    self.write(", ");
27459                                }
27460                                self.write_indent();
27461                                self.generate_expression(item)?;
27462                            }
27463                            self.write_newline();
27464                            self.indent_level -= 1;
27465                        } else {
27466                            for (i, item) in t.expressions.iter().enumerate() {
27467                                if i > 0 {
27468                                    self.write(", ");
27469                                }
27470                                self.generate_expression(item)?;
27471                            }
27472                        }
27473                        self.write(")");
27474                    }
27475                    _ => self.generate_expression(&eq.right)?,
27476                }
27477                Ok(())
27478            }
27479            Expression::Column(c) => {
27480                // Standalone keyword like COMPRESSION
27481                self.write(&c.name.name);
27482                Ok(())
27483            }
27484            _ => self.generate_expression(expr),
27485        }
27486    }
27487
27488    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
27489    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
27490    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
27491        match expr {
27492            Expression::Column(c) => {
27493                // Output identifier, preserving quotes if originally quoted
27494                if c.name.quoted {
27495                    self.write("\"");
27496                    self.write(&c.name.name);
27497                    self.write("\"");
27498                } else {
27499                    self.write(&c.name.name);
27500                }
27501                Ok(())
27502            }
27503            Expression::Identifier(id) => {
27504                // Output identifier, preserving quotes if originally quoted
27505                if id.quoted {
27506                    self.write("\"");
27507                    self.write(&id.name);
27508                    self.write("\"");
27509                } else {
27510                    self.write(&id.name);
27511                }
27512                Ok(())
27513            }
27514            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27515                let Literal::String(s) = lit.as_ref() else {
27516                    unreachable!()
27517                };
27518                // Output string with quotes
27519                self.write("'");
27520                self.write(s);
27521                self.write("'");
27522                Ok(())
27523            }
27524            _ => self.generate_expression(expr),
27525        }
27526    }
27527
27528    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
27529        self.write_keyword(&e.name);
27530        if let Some(ref value) = e.value {
27531            if e.eq {
27532                self.write(" = ");
27533            } else {
27534                self.write(" ");
27535            }
27536            self.generate_expression(value)?;
27537        }
27538        if !e.values.is_empty() {
27539            if e.eq {
27540                self.write(" = ");
27541            } else {
27542                self.write(" ");
27543            }
27544            self.write("(");
27545            for (i, v) in e.values.iter().enumerate() {
27546                if i > 0 {
27547                    self.write(", ");
27548                }
27549                self.generate_expression(v)?;
27550            }
27551            self.write(")");
27552        }
27553        Ok(())
27554    }
27555
27556    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
27557        // CORR(this, expression)
27558        self.write_keyword("CORR");
27559        self.write("(");
27560        self.generate_expression(&e.this)?;
27561        self.write(", ");
27562        self.generate_expression(&e.expression)?;
27563        self.write(")");
27564        Ok(())
27565    }
27566
27567    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27568        // COSINE_DISTANCE(this, expression)
27569        self.write_keyword("COSINE_DISTANCE");
27570        self.write("(");
27571        self.generate_expression(&e.this)?;
27572        self.write(", ");
27573        self.generate_expression(&e.expression)?;
27574        self.write(")");
27575        Ok(())
27576    }
27577
27578    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27579        // COVAR_POP(this, expression)
27580        self.write_keyword("COVAR_POP");
27581        self.write("(");
27582        self.generate_expression(&e.this)?;
27583        self.write(", ");
27584        self.generate_expression(&e.expression)?;
27585        self.write(")");
27586        Ok(())
27587    }
27588
27589    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27590        // COVAR_SAMP(this, expression)
27591        self.write_keyword("COVAR_SAMP");
27592        self.write("(");
27593        self.generate_expression(&e.this)?;
27594        self.write(", ");
27595        self.generate_expression(&e.expression)?;
27596        self.write(")");
27597        Ok(())
27598    }
27599
27600    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27601        // CREDENTIALS (key1='value1', key2='value2')
27602        self.write_keyword("CREDENTIALS");
27603        self.write(" (");
27604        for (i, (key, value)) in e.credentials.iter().enumerate() {
27605            if i > 0 {
27606                self.write(", ");
27607            }
27608            self.write(key);
27609            self.write("='");
27610            self.write(value);
27611            self.write("'");
27612        }
27613        self.write(")");
27614        Ok(())
27615    }
27616
27617    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27618        // CREDENTIALS=(expressions)
27619        self.write_keyword("CREDENTIALS");
27620        self.write("=(");
27621        for (i, expr) in e.expressions.iter().enumerate() {
27622            if i > 0 {
27623                self.write(", ");
27624            }
27625            self.generate_expression(expr)?;
27626        }
27627        self.write(")");
27628        Ok(())
27629    }
27630
27631    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27632        use crate::dialects::DialectType;
27633
27634        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27635        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27636        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27637            self.generate_expression(&e.this)?;
27638            self.write_space();
27639            self.write_keyword("AS");
27640            self.write_space();
27641            self.generate_identifier(&e.alias)?;
27642            return Ok(());
27643        }
27644        self.write(&e.alias.name);
27645
27646        // BigQuery doesn't support column aliases in CTE definitions
27647        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27648
27649        if !e.columns.is_empty() && !skip_cte_columns {
27650            self.write("(");
27651            for (i, col) in e.columns.iter().enumerate() {
27652                if i > 0 {
27653                    self.write(", ");
27654                }
27655                self.write(&col.name);
27656            }
27657            self.write(")");
27658        }
27659        // USING KEY (columns) for DuckDB recursive CTEs
27660        if !e.key_expressions.is_empty() {
27661            self.write_space();
27662            self.write_keyword("USING KEY");
27663            self.write(" (");
27664            for (i, key) in e.key_expressions.iter().enumerate() {
27665                if i > 0 {
27666                    self.write(", ");
27667                }
27668                self.write(&key.name);
27669            }
27670            self.write(")");
27671        }
27672        self.write_space();
27673        self.write_keyword("AS");
27674        self.write_space();
27675        if let Some(materialized) = e.materialized {
27676            if materialized {
27677                self.write_keyword("MATERIALIZED");
27678            } else {
27679                self.write_keyword("NOT MATERIALIZED");
27680            }
27681            self.write_space();
27682        }
27683        self.write("(");
27684        self.generate_expression(&e.this)?;
27685        self.write(")");
27686        Ok(())
27687    }
27688
27689    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27690        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27691        if e.expressions.is_empty() {
27692            self.write_keyword("WITH CUBE");
27693        } else {
27694            self.write_keyword("CUBE");
27695            self.write("(");
27696            for (i, expr) in e.expressions.iter().enumerate() {
27697                if i > 0 {
27698                    self.write(", ");
27699                }
27700                self.generate_expression(expr)?;
27701            }
27702            self.write(")");
27703        }
27704        Ok(())
27705    }
27706
27707    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27708        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27709        self.write_keyword("CURRENT_DATETIME");
27710        if let Some(this) = &e.this {
27711            self.write("(");
27712            self.generate_expression(this)?;
27713            self.write(")");
27714        }
27715        Ok(())
27716    }
27717
27718    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27719        // CURRENT_SCHEMA - no arguments
27720        self.write_keyword("CURRENT_SCHEMA");
27721        Ok(())
27722    }
27723
27724    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27725        // CURRENT_SCHEMAS(include_implicit)
27726        self.write_keyword("CURRENT_SCHEMAS");
27727        self.write("(");
27728        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27729        if !matches!(
27730            self.config.dialect,
27731            Some(crate::dialects::DialectType::Snowflake)
27732        ) {
27733            if let Some(this) = &e.this {
27734                self.generate_expression(this)?;
27735            }
27736        }
27737        self.write(")");
27738        Ok(())
27739    }
27740
27741    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27742        // CURRENT_USER or CURRENT_USER()
27743        self.write_keyword("CURRENT_USER");
27744        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27745        let needs_parens = e.this.is_some()
27746            || matches!(
27747                self.config.dialect,
27748                Some(DialectType::Snowflake)
27749                    | Some(DialectType::Spark)
27750                    | Some(DialectType::Hive)
27751                    | Some(DialectType::DuckDB)
27752                    | Some(DialectType::BigQuery)
27753                    | Some(DialectType::MySQL)
27754                    | Some(DialectType::Databricks)
27755            );
27756        if needs_parens {
27757            self.write("()");
27758        }
27759        Ok(())
27760    }
27761
27762    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27763        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27764        if self.config.dialect == Some(DialectType::Solr) {
27765            self.generate_expression(&e.this)?;
27766            self.write(" ");
27767            self.write_keyword("OR");
27768            self.write(" ");
27769            self.generate_expression(&e.expression)?;
27770        } else if self.config.dialect == Some(DialectType::MySQL) {
27771            self.generate_mysql_concat_from_dpipe(e)?;
27772        } else {
27773            // String concatenation: this || expression
27774            self.generate_expression(&e.this)?;
27775            self.write(" || ");
27776            self.generate_expression(&e.expression)?;
27777        }
27778        Ok(())
27779    }
27780
27781    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27782        // DATABLOCKSIZE=... (Teradata)
27783        self.write_keyword("DATABLOCKSIZE");
27784        self.write("=");
27785        if let Some(size) = e.size {
27786            self.write(&size.to_string());
27787            if let Some(units) = &e.units {
27788                self.write_space();
27789                self.generate_expression(units)?;
27790            }
27791        } else if e.minimum.is_some() {
27792            self.write_keyword("MINIMUM");
27793        } else if e.maximum.is_some() {
27794            self.write_keyword("MAXIMUM");
27795        } else if e.default.is_some() {
27796            self.write_keyword("DEFAULT");
27797        }
27798        Ok(())
27799    }
27800
27801    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27802        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27803        self.write_keyword("DATA_DELETION");
27804        self.write("=");
27805
27806        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27807        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27808
27809        if is_on {
27810            self.write_keyword("ON");
27811            if has_options {
27812                self.write("(");
27813                let mut first = true;
27814                if let Some(filter_column) = &e.filter_column {
27815                    self.write_keyword("FILTER_COLUMN");
27816                    self.write("=");
27817                    self.generate_expression(filter_column)?;
27818                    first = false;
27819                }
27820                if let Some(retention_period) = &e.retention_period {
27821                    if !first {
27822                        self.write(", ");
27823                    }
27824                    self.write_keyword("RETENTION_PERIOD");
27825                    self.write("=");
27826                    self.generate_expression(retention_period)?;
27827                }
27828                self.write(")");
27829            }
27830        } else {
27831            self.write_keyword("OFF");
27832        }
27833        Ok(())
27834    }
27835
27836    /// Generate a Date function expression
27837    /// For Exasol: {d'value'} -> TO_DATE('value')
27838    /// For other dialects: DATE('value')
27839    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27840        use crate::dialects::DialectType;
27841        use crate::expressions::Literal;
27842
27843        match self.config.dialect {
27844            // Exasol uses TO_DATE for Date expressions
27845            Some(DialectType::Exasol) => {
27846                self.write_keyword("TO_DATE");
27847                self.write("(");
27848                // Extract the string value from the expression if it's a string literal
27849                match &e.this {
27850                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27851                        let Literal::String(s) = lit.as_ref() else {
27852                            unreachable!()
27853                        };
27854                        self.write("'");
27855                        self.write(s);
27856                        self.write("'");
27857                    }
27858                    _ => {
27859                        self.generate_expression(&e.this)?;
27860                    }
27861                }
27862                self.write(")");
27863            }
27864            // Standard: DATE(value)
27865            _ => {
27866                self.write_keyword("DATE");
27867                self.write("(");
27868                self.generate_expression(&e.this)?;
27869                self.write(")");
27870            }
27871        }
27872        Ok(())
27873    }
27874
27875    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27876        // DATE_BIN(interval, timestamp[, origin])
27877        self.write_keyword("DATE_BIN");
27878        self.write("(");
27879        self.generate_expression(&e.this)?;
27880        self.write(", ");
27881        self.generate_expression(&e.expression)?;
27882        if let Some(origin) = &e.origin {
27883            self.write(", ");
27884            self.generate_expression(origin)?;
27885        }
27886        self.write(")");
27887        Ok(())
27888    }
27889
27890    fn generate_date_format_column_constraint(
27891        &mut self,
27892        e: &DateFormatColumnConstraint,
27893    ) -> Result<()> {
27894        // FORMAT 'format_string' (Teradata)
27895        self.write_keyword("FORMAT");
27896        self.write_space();
27897        self.generate_expression(&e.this)?;
27898        Ok(())
27899    }
27900
27901    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27902        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27903        self.write_keyword("DATE_FROM_PARTS");
27904        self.write("(");
27905        let mut first = true;
27906        if let Some(year) = &e.year {
27907            self.generate_expression(year)?;
27908            first = false;
27909        }
27910        if let Some(month) = &e.month {
27911            if !first {
27912                self.write(", ");
27913            }
27914            self.generate_expression(month)?;
27915            first = false;
27916        }
27917        if let Some(day) = &e.day {
27918            if !first {
27919                self.write(", ");
27920            }
27921            self.generate_expression(day)?;
27922        }
27923        self.write(")");
27924        Ok(())
27925    }
27926
27927    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27928        // DATETIME(this) or DATETIME(this, expression)
27929        self.write_keyword("DATETIME");
27930        self.write("(");
27931        self.generate_expression(&e.this)?;
27932        if let Some(expr) = &e.expression {
27933            self.write(", ");
27934            self.generate_expression(expr)?;
27935        }
27936        self.write(")");
27937        Ok(())
27938    }
27939
27940    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27941        // DATETIME_ADD(this, expression, unit)
27942        self.write_keyword("DATETIME_ADD");
27943        self.write("(");
27944        self.generate_expression(&e.this)?;
27945        self.write(", ");
27946        self.generate_expression(&e.expression)?;
27947        if let Some(unit) = &e.unit {
27948            self.write(", ");
27949            self.write_keyword(unit);
27950        }
27951        self.write(")");
27952        Ok(())
27953    }
27954
27955    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27956        // DATETIME_DIFF(this, expression, unit)
27957        self.write_keyword("DATETIME_DIFF");
27958        self.write("(");
27959        self.generate_expression(&e.this)?;
27960        self.write(", ");
27961        self.generate_expression(&e.expression)?;
27962        if let Some(unit) = &e.unit {
27963            self.write(", ");
27964            self.write_keyword(unit);
27965        }
27966        self.write(")");
27967        Ok(())
27968    }
27969
27970    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27971        // DATETIME_SUB(this, expression, unit)
27972        self.write_keyword("DATETIME_SUB");
27973        self.write("(");
27974        self.generate_expression(&e.this)?;
27975        self.write(", ");
27976        self.generate_expression(&e.expression)?;
27977        if let Some(unit) = &e.unit {
27978            self.write(", ");
27979            self.write_keyword(unit);
27980        }
27981        self.write(")");
27982        Ok(())
27983    }
27984
27985    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27986        // DATETIME_TRUNC(this, unit, zone)
27987        self.write_keyword("DATETIME_TRUNC");
27988        self.write("(");
27989        self.generate_expression(&e.this)?;
27990        self.write(", ");
27991        self.write_keyword(&e.unit);
27992        if let Some(zone) = &e.zone {
27993            self.write(", ");
27994            self.generate_expression(zone)?;
27995        }
27996        self.write(")");
27997        Ok(())
27998    }
27999
28000    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28001        // DAYNAME(this)
28002        self.write_keyword("DAYNAME");
28003        self.write("(");
28004        self.generate_expression(&e.this)?;
28005        self.write(")");
28006        Ok(())
28007    }
28008
28009    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28010        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28011        self.write_keyword("DECLARE");
28012        self.write_space();
28013        if e.replace {
28014            self.write_keyword("OR");
28015            self.write_space();
28016            self.write_keyword("REPLACE");
28017            self.write_space();
28018        }
28019        for (i, expr) in e.expressions.iter().enumerate() {
28020            if i > 0 {
28021                self.write(", ");
28022            }
28023            self.generate_expression(expr)?;
28024        }
28025        Ok(())
28026    }
28027
28028    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28029        use crate::dialects::DialectType;
28030
28031        // variable TYPE [DEFAULT default]
28032        self.generate_expression(&e.this)?;
28033        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28034        for name in &e.additional_names {
28035            self.write(", ");
28036            self.generate_expression(name)?;
28037        }
28038        if let Some(kind) = &e.kind {
28039            self.write_space();
28040            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28041            // TSQL: Always includes AS (normalization)
28042            // Others: Include AS if present in original
28043            match self.config.dialect {
28044                Some(DialectType::BigQuery) => {
28045                    self.write(kind);
28046                }
28047                Some(DialectType::TSQL) => {
28048                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28049                    // Normalize INT to INTEGER for simple declarations
28050                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28051                    let is_complex_table = kind.starts_with("TABLE")
28052                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28053                    if is_complex_table {
28054                        self.write(kind);
28055                    } else if kind == "INT" {
28056                        self.write("INTEGER");
28057                    } else if kind.starts_with("TABLE") {
28058                        // Normalize INT to INTEGER inside simple TABLE column definitions
28059                        let normalized = kind
28060                            .replace(" INT ", " INTEGER ")
28061                            .replace(" INT,", " INTEGER,")
28062                            .replace(" INT)", " INTEGER)")
28063                            .replace("(INT ", "(INTEGER ");
28064                        self.write(&normalized);
28065                    } else {
28066                        self.write(kind);
28067                    }
28068                }
28069                _ => {
28070                    if e.has_as {
28071                        self.write_keyword("AS");
28072                        self.write_space();
28073                    }
28074                    self.write(kind);
28075                }
28076            }
28077        }
28078        if let Some(default) = &e.default {
28079            // BigQuery uses DEFAULT, others use =
28080            match self.config.dialect {
28081                Some(DialectType::BigQuery) => {
28082                    self.write_space();
28083                    self.write_keyword("DEFAULT");
28084                    self.write_space();
28085                }
28086                _ => {
28087                    self.write(" = ");
28088                }
28089            }
28090            self.generate_expression(default)?;
28091        }
28092        Ok(())
28093    }
28094
28095    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28096        // DECODE(expr, search1, result1, search2, result2, ..., default)
28097        self.write_keyword("DECODE");
28098        self.write("(");
28099        for (i, expr) in e.expressions.iter().enumerate() {
28100            if i > 0 {
28101                self.write(", ");
28102            }
28103            self.generate_expression(expr)?;
28104        }
28105        self.write(")");
28106        Ok(())
28107    }
28108
28109    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28110        // DECOMPRESS(expr, 'method')
28111        self.write_keyword("DECOMPRESS");
28112        self.write("(");
28113        self.generate_expression(&e.this)?;
28114        self.write(", '");
28115        self.write(&e.method);
28116        self.write("')");
28117        Ok(())
28118    }
28119
28120    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28121        // DECOMPRESS(expr, 'method')
28122        self.write_keyword("DECOMPRESS");
28123        self.write("(");
28124        self.generate_expression(&e.this)?;
28125        self.write(", '");
28126        self.write(&e.method);
28127        self.write("')");
28128        Ok(())
28129    }
28130
28131    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28132        // DECRYPT(value, passphrase [, aad [, algorithm]])
28133        self.write_keyword("DECRYPT");
28134        self.write("(");
28135        self.generate_expression(&e.this)?;
28136        if let Some(passphrase) = &e.passphrase {
28137            self.write(", ");
28138            self.generate_expression(passphrase)?;
28139        }
28140        if let Some(aad) = &e.aad {
28141            self.write(", ");
28142            self.generate_expression(aad)?;
28143        }
28144        if let Some(method) = &e.encryption_method {
28145            self.write(", ");
28146            self.generate_expression(method)?;
28147        }
28148        self.write(")");
28149        Ok(())
28150    }
28151
28152    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28153        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28154        self.write_keyword("DECRYPT_RAW");
28155        self.write("(");
28156        self.generate_expression(&e.this)?;
28157        if let Some(key) = &e.key {
28158            self.write(", ");
28159            self.generate_expression(key)?;
28160        }
28161        if let Some(iv) = &e.iv {
28162            self.write(", ");
28163            self.generate_expression(iv)?;
28164        }
28165        if let Some(aad) = &e.aad {
28166            self.write(", ");
28167            self.generate_expression(aad)?;
28168        }
28169        if let Some(method) = &e.encryption_method {
28170            self.write(", ");
28171            self.generate_expression(method)?;
28172        }
28173        self.write(")");
28174        Ok(())
28175    }
28176
28177    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28178        // DEFINER = user
28179        self.write_keyword("DEFINER");
28180        self.write(" = ");
28181        self.generate_expression(&e.this)?;
28182        Ok(())
28183    }
28184
28185    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28186        // Python: DETACH[DATABASE IF EXISTS] this
28187        self.write_keyword("DETACH");
28188        if e.exists {
28189            self.write_keyword(" DATABASE IF EXISTS");
28190        }
28191        self.write_space();
28192        self.generate_expression(&e.this)?;
28193        Ok(())
28194    }
28195
28196    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28197        let property_name = match e.this.as_ref() {
28198            Expression::Identifier(id) => id.name.as_str(),
28199            Expression::Var(v) => v.this.as_str(),
28200            _ => "DICTIONARY",
28201        };
28202        self.write_keyword(property_name);
28203        self.write("(");
28204        self.write(&e.kind);
28205        if let Some(settings) = &e.settings {
28206            self.write("(");
28207            if let Expression::Tuple(t) = settings.as_ref() {
28208                if self.config.pretty && !t.expressions.is_empty() {
28209                    self.write_newline();
28210                    self.indent_level += 1;
28211                    for (i, pair) in t.expressions.iter().enumerate() {
28212                        if i > 0 {
28213                            self.write(",");
28214                            self.write_newline();
28215                        }
28216                        self.write_indent();
28217                        if let Expression::Tuple(pair_tuple) = pair {
28218                            if let Some(k) = pair_tuple.expressions.first() {
28219                                self.generate_expression(k)?;
28220                            }
28221                            if let Some(v) = pair_tuple.expressions.get(1) {
28222                                self.write(" ");
28223                                self.generate_expression(v)?;
28224                            }
28225                        } else {
28226                            self.generate_expression(pair)?;
28227                        }
28228                    }
28229                    self.indent_level -= 1;
28230                    self.write_newline();
28231                    self.write_indent();
28232                } else {
28233                    for (i, pair) in t.expressions.iter().enumerate() {
28234                        if i > 0 {
28235                            // ClickHouse dict properties are space-separated, not comma-separated
28236                            self.write(" ");
28237                        }
28238                        if let Expression::Tuple(pair_tuple) = pair {
28239                            if let Some(k) = pair_tuple.expressions.first() {
28240                                self.generate_expression(k)?;
28241                            }
28242                            if let Some(v) = pair_tuple.expressions.get(1) {
28243                                self.write(" ");
28244                                self.generate_expression(v)?;
28245                            }
28246                        } else {
28247                            self.generate_expression(pair)?;
28248                        }
28249                    }
28250                }
28251            } else {
28252                self.generate_expression(settings)?;
28253            }
28254            self.write(")");
28255        } else {
28256            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28257            self.write("()");
28258        }
28259        self.write(")");
28260        Ok(())
28261    }
28262
28263    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28264        let property_name = match e.this.as_ref() {
28265            Expression::Identifier(id) => id.name.as_str(),
28266            Expression::Var(v) => v.this.as_str(),
28267            _ => "RANGE",
28268        };
28269        self.write_keyword(property_name);
28270        self.write("(");
28271        if let Some(min) = &e.min {
28272            self.write_keyword("MIN");
28273            self.write_space();
28274            self.generate_expression(min)?;
28275        }
28276        if let Some(max) = &e.max {
28277            self.write_space();
28278            self.write_keyword("MAX");
28279            self.write_space();
28280            self.generate_expression(max)?;
28281        }
28282        self.write(")");
28283        Ok(())
28284    }
28285
28286    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28287        // Python: {local}DIRECTORY {this}{row_format}
28288        if e.local.is_some() {
28289            self.write_keyword("LOCAL ");
28290        }
28291        self.write_keyword("DIRECTORY");
28292        self.write_space();
28293        self.generate_expression(&e.this)?;
28294        if let Some(row_format) = &e.row_format {
28295            self.write_space();
28296            self.generate_expression(row_format)?;
28297        }
28298        Ok(())
28299    }
28300
28301    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28302        // Redshift: DISTKEY(column)
28303        self.write_keyword("DISTKEY");
28304        self.write("(");
28305        self.generate_expression(&e.this)?;
28306        self.write(")");
28307        Ok(())
28308    }
28309
28310    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28311        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28312        self.write_keyword("DISTSTYLE");
28313        self.write_space();
28314        self.generate_expression(&e.this)?;
28315        Ok(())
28316    }
28317
28318    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28319        // Python: "DISTRIBUTE BY" expressions
28320        self.write_keyword("DISTRIBUTE BY");
28321        self.write_space();
28322        for (i, expr) in e.expressions.iter().enumerate() {
28323            if i > 0 {
28324                self.write(", ");
28325            }
28326            self.generate_expression(expr)?;
28327        }
28328        Ok(())
28329    }
28330
28331    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28332        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28333        self.write_keyword("DISTRIBUTED BY");
28334        self.write_space();
28335        self.write(&e.kind);
28336        if !e.expressions.is_empty() {
28337            self.write(" (");
28338            for (i, expr) in e.expressions.iter().enumerate() {
28339                if i > 0 {
28340                    self.write(", ");
28341                }
28342                self.generate_expression(expr)?;
28343            }
28344            self.write(")");
28345        }
28346        if let Some(buckets) = &e.buckets {
28347            self.write_space();
28348            self.write_keyword("BUCKETS");
28349            self.write_space();
28350            self.generate_expression(buckets)?;
28351        }
28352        if let Some(order) = &e.order {
28353            self.write_space();
28354            self.generate_expression(order)?;
28355        }
28356        Ok(())
28357    }
28358
28359    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28360        // DOT_PRODUCT(vector1, vector2)
28361        self.write_keyword("DOT_PRODUCT");
28362        self.write("(");
28363        self.generate_expression(&e.this)?;
28364        self.write(", ");
28365        self.generate_expression(&e.expression)?;
28366        self.write(")");
28367        Ok(())
28368    }
28369
28370    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28371        // Python: DROP{IF EXISTS }expressions
28372        self.write_keyword("DROP");
28373        if e.exists {
28374            self.write_keyword(" IF EXISTS ");
28375        } else {
28376            self.write_space();
28377        }
28378        for (i, expr) in e.expressions.iter().enumerate() {
28379            if i > 0 {
28380                self.write(", ");
28381            }
28382            self.generate_expression(expr)?;
28383        }
28384        Ok(())
28385    }
28386
28387    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28388        // Python: DUPLICATE KEY (expressions)
28389        self.write_keyword("DUPLICATE KEY");
28390        self.write(" (");
28391        for (i, expr) in e.expressions.iter().enumerate() {
28392            if i > 0 {
28393                self.write(", ");
28394            }
28395            self.generate_expression(expr)?;
28396        }
28397        self.write(")");
28398        Ok(())
28399    }
28400
28401    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28402        // ELT(index, str1, str2, ...)
28403        self.write_keyword("ELT");
28404        self.write("(");
28405        self.generate_expression(&e.this)?;
28406        for expr in &e.expressions {
28407            self.write(", ");
28408            self.generate_expression(expr)?;
28409        }
28410        self.write(")");
28411        Ok(())
28412    }
28413
28414    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28415        // ENCODE(string, charset)
28416        self.write_keyword("ENCODE");
28417        self.write("(");
28418        self.generate_expression(&e.this)?;
28419        if let Some(charset) = &e.charset {
28420            self.write(", ");
28421            self.generate_expression(charset)?;
28422        }
28423        self.write(")");
28424        Ok(())
28425    }
28426
28427    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28428        // Python: [KEY ]ENCODE this [properties]
28429        if e.key.is_some() {
28430            self.write_keyword("KEY ");
28431        }
28432        self.write_keyword("ENCODE");
28433        self.write_space();
28434        self.generate_expression(&e.this)?;
28435        if !e.properties.is_empty() {
28436            self.write(" (");
28437            for (i, prop) in e.properties.iter().enumerate() {
28438                if i > 0 {
28439                    self.write(", ");
28440                }
28441                self.generate_expression(prop)?;
28442            }
28443            self.write(")");
28444        }
28445        Ok(())
28446    }
28447
28448    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28449        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28450        self.write_keyword("ENCRYPT");
28451        self.write("(");
28452        self.generate_expression(&e.this)?;
28453        if let Some(passphrase) = &e.passphrase {
28454            self.write(", ");
28455            self.generate_expression(passphrase)?;
28456        }
28457        if let Some(aad) = &e.aad {
28458            self.write(", ");
28459            self.generate_expression(aad)?;
28460        }
28461        if let Some(method) = &e.encryption_method {
28462            self.write(", ");
28463            self.generate_expression(method)?;
28464        }
28465        self.write(")");
28466        Ok(())
28467    }
28468
28469    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
28470        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28471        self.write_keyword("ENCRYPT_RAW");
28472        self.write("(");
28473        self.generate_expression(&e.this)?;
28474        if let Some(key) = &e.key {
28475            self.write(", ");
28476            self.generate_expression(key)?;
28477        }
28478        if let Some(iv) = &e.iv {
28479            self.write(", ");
28480            self.generate_expression(iv)?;
28481        }
28482        if let Some(aad) = &e.aad {
28483            self.write(", ");
28484            self.generate_expression(aad)?;
28485        }
28486        if let Some(method) = &e.encryption_method {
28487            self.write(", ");
28488            self.generate_expression(method)?;
28489        }
28490        self.write(")");
28491        Ok(())
28492    }
28493
28494    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
28495        // MySQL: ENGINE = InnoDB
28496        self.write_keyword("ENGINE");
28497        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
28498            self.write("=");
28499        } else {
28500            self.write(" = ");
28501        }
28502        self.generate_expression(&e.this)?;
28503        Ok(())
28504    }
28505
28506    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
28507        // ENVIRONMENT (expressions)
28508        self.write_keyword("ENVIRONMENT");
28509        self.write(" (");
28510        for (i, expr) in e.expressions.iter().enumerate() {
28511            if i > 0 {
28512                self.write(", ");
28513            }
28514            self.generate_expression(expr)?;
28515        }
28516        self.write(")");
28517        Ok(())
28518    }
28519
28520    fn generate_ephemeral_column_constraint(
28521        &mut self,
28522        e: &EphemeralColumnConstraint,
28523    ) -> Result<()> {
28524        // MySQL: EPHEMERAL [expr]
28525        self.write_keyword("EPHEMERAL");
28526        if let Some(this) = &e.this {
28527            self.write_space();
28528            self.generate_expression(this)?;
28529        }
28530        Ok(())
28531    }
28532
28533    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
28534        // Snowflake: EQUAL_NULL(a, b)
28535        self.write_keyword("EQUAL_NULL");
28536        self.write("(");
28537        self.generate_expression(&e.this)?;
28538        self.write(", ");
28539        self.generate_expression(&e.expression)?;
28540        self.write(")");
28541        Ok(())
28542    }
28543
28544    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
28545        use crate::dialects::DialectType;
28546
28547        // PostgreSQL uses <-> operator syntax
28548        match self.config.dialect {
28549            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
28550                self.generate_expression(&e.this)?;
28551                self.write(" <-> ");
28552                self.generate_expression(&e.expression)?;
28553            }
28554            _ => {
28555                // Other dialects use EUCLIDEAN_DISTANCE function
28556                self.write_keyword("EUCLIDEAN_DISTANCE");
28557                self.write("(");
28558                self.generate_expression(&e.this)?;
28559                self.write(", ");
28560                self.generate_expression(&e.expression)?;
28561                self.write(")");
28562            }
28563        }
28564        Ok(())
28565    }
28566
28567    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28568        // EXECUTE AS CALLER|OWNER|user
28569        self.write_keyword("EXECUTE AS");
28570        self.write_space();
28571        self.generate_expression(&e.this)?;
28572        Ok(())
28573    }
28574
28575    fn generate_export(&mut self, e: &Export) -> Result<()> {
28576        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28577        self.write_keyword("EXPORT DATA");
28578        if let Some(connection) = &e.connection {
28579            self.write_space();
28580            self.write_keyword("WITH CONNECTION");
28581            self.write_space();
28582            self.generate_expression(connection)?;
28583        }
28584        if !e.options.is_empty() {
28585            self.write_space();
28586            self.generate_options_clause(&e.options)?;
28587        }
28588        self.write_space();
28589        self.write_keyword("AS");
28590        self.write_space();
28591        self.generate_expression(&e.this)?;
28592        Ok(())
28593    }
28594
28595    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28596        // EXTERNAL [this]
28597        self.write_keyword("EXTERNAL");
28598        if let Some(this) = &e.this {
28599            self.write_space();
28600            self.generate_expression(this)?;
28601        }
28602        Ok(())
28603    }
28604
28605    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28606        // Python: {no}FALLBACK{protection}
28607        if e.no.is_some() {
28608            self.write_keyword("NO ");
28609        }
28610        self.write_keyword("FALLBACK");
28611        if e.protection.is_some() {
28612            self.write_keyword(" PROTECTION");
28613        }
28614        Ok(())
28615    }
28616
28617    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28618        // BigQuery: FARM_FINGERPRINT(value)
28619        self.write_keyword("FARM_FINGERPRINT");
28620        self.write("(");
28621        for (i, expr) in e.expressions.iter().enumerate() {
28622            if i > 0 {
28623                self.write(", ");
28624            }
28625            self.generate_expression(expr)?;
28626        }
28627        self.write(")");
28628        Ok(())
28629    }
28630
28631    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28632        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28633        self.write_keyword("FEATURES_AT_TIME");
28634        self.write("(");
28635        self.generate_expression(&e.this)?;
28636        if let Some(time) = &e.time {
28637            self.write(", ");
28638            self.generate_expression(time)?;
28639        }
28640        if let Some(num_rows) = &e.num_rows {
28641            self.write(", ");
28642            self.generate_expression(num_rows)?;
28643        }
28644        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28645            self.write(", ");
28646            self.generate_expression(ignore_nulls)?;
28647        }
28648        self.write(")");
28649        Ok(())
28650    }
28651
28652    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28653        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28654        let use_limit = !e.percent
28655            && !e.with_ties
28656            && e.count.is_some()
28657            && matches!(
28658                self.config.dialect,
28659                Some(DialectType::Spark)
28660                    | Some(DialectType::Hive)
28661                    | Some(DialectType::DuckDB)
28662                    | Some(DialectType::SQLite)
28663                    | Some(DialectType::MySQL)
28664                    | Some(DialectType::BigQuery)
28665                    | Some(DialectType::Databricks)
28666                    | Some(DialectType::StarRocks)
28667                    | Some(DialectType::Doris)
28668                    | Some(DialectType::Athena)
28669                    | Some(DialectType::ClickHouse)
28670            );
28671
28672        if use_limit {
28673            self.write_keyword("LIMIT");
28674            self.write_space();
28675            self.generate_expression(e.count.as_ref().unwrap())?;
28676            return Ok(());
28677        }
28678
28679        // Python: FETCH direction count limit_options
28680        self.write_keyword("FETCH");
28681        if !e.direction.is_empty() {
28682            self.write_space();
28683            self.write_keyword(&e.direction);
28684        }
28685        if let Some(count) = &e.count {
28686            self.write_space();
28687            self.generate_expression(count)?;
28688        }
28689        // Generate PERCENT, ROWS, WITH TIES/ONLY
28690        if e.percent {
28691            self.write_keyword(" PERCENT");
28692        }
28693        if e.rows {
28694            self.write_keyword(" ROWS");
28695        }
28696        if e.with_ties {
28697            self.write_keyword(" WITH TIES");
28698        } else if e.rows {
28699            self.write_keyword(" ONLY");
28700        } else {
28701            self.write_keyword(" ROWS ONLY");
28702        }
28703        Ok(())
28704    }
28705
28706    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28707        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28708        // For Spark/Databricks without hive_format: USING this
28709        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28710        if e.hive_format.is_some() {
28711            // Hive format: STORED AS ...
28712            self.write_keyword("STORED AS");
28713            self.write_space();
28714            if let Some(this) = &e.this {
28715                // Uppercase the format name (e.g., parquet -> PARQUET)
28716                if let Expression::Identifier(id) = this.as_ref() {
28717                    self.write_keyword(&id.name.to_ascii_uppercase());
28718                } else {
28719                    self.generate_expression(this)?;
28720                }
28721            }
28722        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28723            // Hive: STORED AS format
28724            self.write_keyword("STORED AS");
28725            self.write_space();
28726            if let Some(this) = &e.this {
28727                if let Expression::Identifier(id) = this.as_ref() {
28728                    self.write_keyword(&id.name.to_ascii_uppercase());
28729                } else {
28730                    self.generate_expression(this)?;
28731                }
28732            }
28733        } else if matches!(
28734            self.config.dialect,
28735            Some(DialectType::Spark) | Some(DialectType::Databricks)
28736        ) {
28737            // Spark/Databricks: USING format (e.g., USING DELTA)
28738            self.write_keyword("USING");
28739            self.write_space();
28740            if let Some(this) = &e.this {
28741                self.generate_expression(this)?;
28742            }
28743        } else {
28744            // Snowflake/standard format
28745            self.write_keyword("FILE_FORMAT");
28746            self.write(" = ");
28747            if let Some(this) = &e.this {
28748                self.generate_expression(this)?;
28749            } else if !e.expressions.is_empty() {
28750                self.write("(");
28751                for (i, expr) in e.expressions.iter().enumerate() {
28752                    if i > 0 {
28753                        self.write(", ");
28754                    }
28755                    self.generate_expression(expr)?;
28756                }
28757                self.write(")");
28758            }
28759        }
28760        Ok(())
28761    }
28762
28763    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28764        // agg_func FILTER(WHERE condition)
28765        self.generate_expression(&e.this)?;
28766        self.write_space();
28767        self.write_keyword("FILTER");
28768        self.write("(");
28769        self.write_keyword("WHERE");
28770        self.write_space();
28771        self.generate_expression(&e.expression)?;
28772        self.write(")");
28773        Ok(())
28774    }
28775
28776    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28777        // FLOAT64(this) or FLOAT64(this, expression)
28778        self.write_keyword("FLOAT64");
28779        self.write("(");
28780        self.generate_expression(&e.this)?;
28781        if let Some(expr) = &e.expression {
28782            self.write(", ");
28783            self.generate_expression(expr)?;
28784        }
28785        self.write(")");
28786        Ok(())
28787    }
28788
28789    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28790        // FOR this DO expression
28791        self.write_keyword("FOR");
28792        self.write_space();
28793        self.generate_expression(&e.this)?;
28794        self.write_space();
28795        self.write_keyword("DO");
28796        self.write_space();
28797        self.generate_expression(&e.expression)?;
28798        Ok(())
28799    }
28800
28801    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28802        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28803        self.write_keyword("FOREIGN KEY");
28804        if !e.expressions.is_empty() {
28805            self.write(" (");
28806            for (i, expr) in e.expressions.iter().enumerate() {
28807                if i > 0 {
28808                    self.write(", ");
28809                }
28810                self.generate_expression(expr)?;
28811            }
28812            self.write(")");
28813        }
28814        if let Some(reference) = &e.reference {
28815            self.write_space();
28816            self.generate_expression(reference)?;
28817        }
28818        if let Some(delete) = &e.delete {
28819            self.write_space();
28820            self.write_keyword("ON DELETE");
28821            self.write_space();
28822            self.generate_expression(delete)?;
28823        }
28824        if let Some(update) = &e.update {
28825            self.write_space();
28826            self.write_keyword("ON UPDATE");
28827            self.write_space();
28828            self.generate_expression(update)?;
28829        }
28830        if !e.options.is_empty() {
28831            self.write_space();
28832            for (i, opt) in e.options.iter().enumerate() {
28833                if i > 0 {
28834                    self.write_space();
28835                }
28836                self.generate_expression(opt)?;
28837            }
28838        }
28839        Ok(())
28840    }
28841
28842    fn generate_format(&mut self, e: &Format) -> Result<()> {
28843        // FORMAT(this, expressions...)
28844        self.write_keyword("FORMAT");
28845        self.write("(");
28846        self.generate_expression(&e.this)?;
28847        for expr in &e.expressions {
28848            self.write(", ");
28849            self.generate_expression(expr)?;
28850        }
28851        self.write(")");
28852        Ok(())
28853    }
28854
28855    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28856        // Teradata: column (FORMAT 'format_string')
28857        self.generate_expression(&e.this)?;
28858        self.write(" (");
28859        self.write_keyword("FORMAT");
28860        self.write(" '");
28861        self.write(&e.format);
28862        self.write("')");
28863        Ok(())
28864    }
28865
28866    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28867        // Python: FREESPACE=this[PERCENT]
28868        self.write_keyword("FREESPACE");
28869        self.write("=");
28870        self.generate_expression(&e.this)?;
28871        if e.percent.is_some() {
28872            self.write_keyword(" PERCENT");
28873        }
28874        Ok(())
28875    }
28876
28877    fn generate_from(&mut self, e: &From) -> Result<()> {
28878        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28879        self.write_keyword("FROM");
28880        self.write_space();
28881
28882        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28883        // But keep commas when TABLESAMPLE is present
28884        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28885        use crate::dialects::DialectType;
28886        let has_tablesample = e
28887            .expressions
28888            .iter()
28889            .any(|expr| matches!(expr, Expression::TableSample(_)));
28890        let is_cross_join_dialect = matches!(
28891            self.config.dialect,
28892            Some(DialectType::BigQuery)
28893                | Some(DialectType::Hive)
28894                | Some(DialectType::Spark)
28895                | Some(DialectType::Databricks)
28896                | Some(DialectType::SQLite)
28897                | Some(DialectType::ClickHouse)
28898        );
28899        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28900            && self.config.source_dialect == self.config.dialect;
28901        let source_is_cross_join_dialect2 = matches!(
28902            self.config.source_dialect,
28903            Some(DialectType::BigQuery)
28904                | Some(DialectType::Hive)
28905                | Some(DialectType::Spark)
28906                | Some(DialectType::Databricks)
28907                | Some(DialectType::SQLite)
28908                | Some(DialectType::ClickHouse)
28909        );
28910        let use_cross_join = !has_tablesample
28911            && is_cross_join_dialect
28912            && (source_is_same_as_target2
28913                || source_is_cross_join_dialect2
28914                || self.config.source_dialect.is_none());
28915
28916        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28917        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28918
28919        for (i, expr) in e.expressions.iter().enumerate() {
28920            if i > 0 {
28921                if use_cross_join {
28922                    self.write(" CROSS JOIN ");
28923                } else {
28924                    self.write(", ");
28925                }
28926            }
28927            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28928                self.write("(");
28929                self.generate_expression(expr)?;
28930                self.write(")");
28931            } else {
28932                self.generate_expression(expr)?;
28933            }
28934            // Output leading comments that were on the table name before FROM
28935            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28936            let leading = Self::extract_table_leading_comments(expr);
28937            for comment in &leading {
28938                self.write_space();
28939                self.write_formatted_comment(comment);
28940            }
28941        }
28942        Ok(())
28943    }
28944
28945    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28946    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28947        match expr {
28948            Expression::Table(t) => t.leading_comments.clone(),
28949            Expression::Pivot(p) => {
28950                if let Expression::Table(t) = &p.this {
28951                    t.leading_comments.clone()
28952                } else {
28953                    Vec::new()
28954                }
28955            }
28956            _ => Vec::new(),
28957        }
28958    }
28959
28960    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28961        // FROM_BASE(this, expression) - convert from base N
28962        self.write_keyword("FROM_BASE");
28963        self.write("(");
28964        self.generate_expression(&e.this)?;
28965        self.write(", ");
28966        self.generate_expression(&e.expression)?;
28967        self.write(")");
28968        Ok(())
28969    }
28970
28971    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28972        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28973        self.generate_expression(&e.this)?;
28974        if let Some(zone) = &e.zone {
28975            self.write_space();
28976            self.write_keyword("AT TIME ZONE");
28977            self.write_space();
28978            self.generate_expression(zone)?;
28979            self.write_space();
28980            self.write_keyword("AT TIME ZONE");
28981            self.write(" 'UTC'");
28982        }
28983        Ok(())
28984    }
28985
28986    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28987        // GAP_FILL(this, ts_column, bucket_width, ...)
28988        self.write_keyword("GAP_FILL");
28989        self.write("(");
28990        self.generate_expression(&e.this)?;
28991        if let Some(ts_column) = &e.ts_column {
28992            self.write(", ");
28993            self.generate_expression(ts_column)?;
28994        }
28995        if let Some(bucket_width) = &e.bucket_width {
28996            self.write(", ");
28997            self.generate_expression(bucket_width)?;
28998        }
28999        if let Some(partitioning_columns) = &e.partitioning_columns {
29000            self.write(", ");
29001            self.generate_expression(partitioning_columns)?;
29002        }
29003        if let Some(value_columns) = &e.value_columns {
29004            self.write(", ");
29005            self.generate_expression(value_columns)?;
29006        }
29007        self.write(")");
29008        Ok(())
29009    }
29010
29011    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29012        // GENERATE_DATE_ARRAY(start, end, step)
29013        self.write_keyword("GENERATE_DATE_ARRAY");
29014        self.write("(");
29015        let mut first = true;
29016        if let Some(start) = &e.start {
29017            self.generate_expression(start)?;
29018            first = false;
29019        }
29020        if let Some(end) = &e.end {
29021            if !first {
29022                self.write(", ");
29023            }
29024            self.generate_expression(end)?;
29025            first = false;
29026        }
29027        if let Some(step) = &e.step {
29028            if !first {
29029                self.write(", ");
29030            }
29031            self.generate_expression(step)?;
29032        }
29033        self.write(")");
29034        Ok(())
29035    }
29036
29037    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29038        // ML.GENERATE_EMBEDDING(model, content, params)
29039        self.write_keyword("ML.GENERATE_EMBEDDING");
29040        self.write("(");
29041        self.generate_expression(&e.this)?;
29042        self.write(", ");
29043        self.generate_expression(&e.expression)?;
29044        if let Some(params) = &e.params_struct {
29045            self.write(", ");
29046            self.generate_expression(params)?;
29047        }
29048        self.write(")");
29049        Ok(())
29050    }
29051
29052    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29053        // Dialect-specific function name
29054        let fn_name = match self.config.dialect {
29055            Some(DialectType::Presto)
29056            | Some(DialectType::Trino)
29057            | Some(DialectType::Athena)
29058            | Some(DialectType::Spark)
29059            | Some(DialectType::Databricks)
29060            | Some(DialectType::Hive) => "SEQUENCE",
29061            _ => "GENERATE_SERIES",
29062        };
29063        self.write_keyword(fn_name);
29064        self.write("(");
29065        let mut first = true;
29066        if let Some(start) = &e.start {
29067            self.generate_expression(start)?;
29068            first = false;
29069        }
29070        if let Some(end) = &e.end {
29071            if !first {
29072                self.write(", ");
29073            }
29074            self.generate_expression(end)?;
29075            first = false;
29076        }
29077        if let Some(step) = &e.step {
29078            if !first {
29079                self.write(", ");
29080            }
29081            // For Presto/Trino: convert WEEK intervals to DAY multiples
29082            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29083            if matches!(
29084                self.config.dialect,
29085                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29086            ) {
29087                if let Some(converted) = self.convert_week_interval_to_day(step) {
29088                    self.generate_expression(&converted)?;
29089                } else {
29090                    self.generate_expression(step)?;
29091                }
29092            } else {
29093                self.generate_expression(step)?;
29094            }
29095        }
29096        self.write(")");
29097        Ok(())
29098    }
29099
29100    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29101    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29102    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29103        use crate::expressions::*;
29104        if let Expression::Interval(ref iv) = expr {
29105            // Check for structured WEEK unit
29106            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29107                unit: IntervalUnit::Week,
29108                ..
29109            }) = &iv.unit
29110            {
29111                // Value is in iv.this
29112                let count = match &iv.this {
29113                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29114                        Literal::String(s) | Literal::Number(s) => s.clone(),
29115                        _ => return None,
29116                    },
29117                    _ => return None,
29118                };
29119                (true, count)
29120            } else if iv.unit.is_none() {
29121                // Check for string-encoded interval like "1 WEEK"
29122                if let Some(Expression::Literal(lit)) = &iv.this {
29123                    if let Literal::String(s) = lit.as_ref() {
29124                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29125                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29126                            (true, parts[0].to_string())
29127                        } else {
29128                            (false, String::new())
29129                        }
29130                    } else {
29131                        (false, String::new())
29132                    }
29133                } else {
29134                    (false, String::new())
29135                }
29136            } else {
29137                (false, String::new())
29138            };
29139
29140            if is_week {
29141                // Build: (N * INTERVAL '7' DAY)
29142                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29143                let day_interval = Expression::Interval(Box::new(Interval {
29144                    this: Some(Expression::Literal(Box::new(Literal::String(
29145                        "7".to_string(),
29146                    )))),
29147                    unit: Some(IntervalUnitSpec::Simple {
29148                        unit: IntervalUnit::Day,
29149                        use_plural: false,
29150                    }),
29151                }));
29152                let mul = Expression::Mul(Box::new(BinaryOp {
29153                    left: count_expr,
29154                    right: day_interval,
29155                    left_comments: vec![],
29156                    operator_comments: vec![],
29157                    trailing_comments: vec![],
29158                    inferred_type: None,
29159                }));
29160                return Some(Expression::Paren(Box::new(Paren {
29161                    this: mul,
29162                    trailing_comments: vec![],
29163                })));
29164            }
29165        }
29166        None
29167    }
29168
29169    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29170        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29171        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29172        self.write("(");
29173        let mut first = true;
29174        if let Some(start) = &e.start {
29175            self.generate_expression(start)?;
29176            first = false;
29177        }
29178        if let Some(end) = &e.end {
29179            if !first {
29180                self.write(", ");
29181            }
29182            self.generate_expression(end)?;
29183            first = false;
29184        }
29185        if let Some(step) = &e.step {
29186            if !first {
29187                self.write(", ");
29188            }
29189            self.generate_expression(step)?;
29190        }
29191        self.write(")");
29192        Ok(())
29193    }
29194
29195    fn generate_generated_as_identity_column_constraint(
29196        &mut self,
29197        e: &GeneratedAsIdentityColumnConstraint,
29198    ) -> Result<()> {
29199        use crate::dialects::DialectType;
29200
29201        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29202        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29203            self.write_keyword("AUTOINCREMENT");
29204            if let Some(start) = &e.start {
29205                self.write_keyword(" START ");
29206                self.generate_expression(start)?;
29207            }
29208            if let Some(increment) = &e.increment {
29209                self.write_keyword(" INCREMENT ");
29210                self.generate_expression(increment)?;
29211            }
29212            return Ok(());
29213        }
29214
29215        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29216        self.write_keyword("GENERATED");
29217        if let Some(this) = &e.this {
29218            // Check if it's a truthy boolean expression
29219            if let Expression::Boolean(b) = this.as_ref() {
29220                if b.value {
29221                    self.write_keyword(" ALWAYS");
29222                } else {
29223                    self.write_keyword(" BY DEFAULT");
29224                    if e.on_null.is_some() {
29225                        self.write_keyword(" ON NULL");
29226                    }
29227                }
29228            } else {
29229                self.write_keyword(" ALWAYS");
29230            }
29231        }
29232        self.write_keyword(" AS IDENTITY");
29233        // Add sequence options if any
29234        let has_options = e.start.is_some()
29235            || e.increment.is_some()
29236            || e.minvalue.is_some()
29237            || e.maxvalue.is_some();
29238        if has_options {
29239            self.write(" (");
29240            let mut first = true;
29241            if let Some(start) = &e.start {
29242                self.write_keyword("START WITH ");
29243                self.generate_expression(start)?;
29244                first = false;
29245            }
29246            if let Some(increment) = &e.increment {
29247                if !first {
29248                    self.write(" ");
29249                }
29250                self.write_keyword("INCREMENT BY ");
29251                self.generate_expression(increment)?;
29252                first = false;
29253            }
29254            if let Some(minvalue) = &e.minvalue {
29255                if !first {
29256                    self.write(" ");
29257                }
29258                self.write_keyword("MINVALUE ");
29259                self.generate_expression(minvalue)?;
29260                first = false;
29261            }
29262            if let Some(maxvalue) = &e.maxvalue {
29263                if !first {
29264                    self.write(" ");
29265                }
29266                self.write_keyword("MAXVALUE ");
29267                self.generate_expression(maxvalue)?;
29268            }
29269            self.write(")");
29270        }
29271        Ok(())
29272    }
29273
29274    fn generate_generated_as_row_column_constraint(
29275        &mut self,
29276        e: &GeneratedAsRowColumnConstraint,
29277    ) -> Result<()> {
29278        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29279        self.write_keyword("GENERATED ALWAYS AS ROW ");
29280        if e.start.is_some() {
29281            self.write_keyword("START");
29282        } else {
29283            self.write_keyword("END");
29284        }
29285        if e.hidden.is_some() {
29286            self.write_keyword(" HIDDEN");
29287        }
29288        Ok(())
29289    }
29290
29291    fn generate_get(&mut self, e: &Get) -> Result<()> {
29292        // GET this target properties
29293        self.write_keyword("GET");
29294        self.write_space();
29295        self.generate_expression(&e.this)?;
29296        if let Some(target) = &e.target {
29297            self.write_space();
29298            self.generate_expression(target)?;
29299        }
29300        for prop in &e.properties {
29301            self.write_space();
29302            self.generate_expression(prop)?;
29303        }
29304        Ok(())
29305    }
29306
29307    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29308        // GetExtract generates bracket access: this[expression]
29309        self.generate_expression(&e.this)?;
29310        self.write("[");
29311        self.generate_expression(&e.expression)?;
29312        self.write("]");
29313        Ok(())
29314    }
29315
29316    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29317        // GETBIT(this, expression) or GET_BIT(this, expression)
29318        self.write_keyword("GETBIT");
29319        self.write("(");
29320        self.generate_expression(&e.this)?;
29321        self.write(", ");
29322        self.generate_expression(&e.expression)?;
29323        self.write(")");
29324        Ok(())
29325    }
29326
29327    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29328        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29329        if e.is_role {
29330            self.write_keyword("ROLE");
29331            self.write_space();
29332        } else if e.is_group {
29333            self.write_keyword("GROUP");
29334            self.write_space();
29335        } else if e.is_share {
29336            self.write_keyword("SHARE");
29337            self.write_space();
29338        }
29339        self.write(&e.name.name);
29340        Ok(())
29341    }
29342
29343    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29344        // privilege(columns) or just privilege
29345        self.generate_expression(&e.this)?;
29346        if !e.expressions.is_empty() {
29347            self.write("(");
29348            for (i, expr) in e.expressions.iter().enumerate() {
29349                if i > 0 {
29350                    self.write(", ");
29351                }
29352                self.generate_expression(expr)?;
29353            }
29354            self.write(")");
29355        }
29356        Ok(())
29357    }
29358
29359    fn generate_group(&mut self, e: &Group) -> Result<()> {
29360        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29361        self.write_keyword("GROUP BY");
29362        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29363        match e.all {
29364            Some(true) => {
29365                self.write_space();
29366                self.write_keyword("ALL");
29367            }
29368            Some(false) => {
29369                self.write_space();
29370                self.write_keyword("DISTINCT");
29371            }
29372            None => {}
29373        }
29374        if !e.expressions.is_empty() {
29375            self.write_space();
29376            for (i, expr) in e.expressions.iter().enumerate() {
29377                if i > 0 {
29378                    self.write(", ");
29379                }
29380                self.generate_expression(expr)?;
29381            }
29382        }
29383        // Handle CUBE, ROLLUP, GROUPING SETS
29384        if let Some(cube) = &e.cube {
29385            if !e.expressions.is_empty() {
29386                self.write(", ");
29387            } else {
29388                self.write_space();
29389            }
29390            self.generate_expression(cube)?;
29391        }
29392        if let Some(rollup) = &e.rollup {
29393            if !e.expressions.is_empty() || e.cube.is_some() {
29394                self.write(", ");
29395            } else {
29396                self.write_space();
29397            }
29398            self.generate_expression(rollup)?;
29399        }
29400        if let Some(grouping_sets) = &e.grouping_sets {
29401            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29402                self.write(", ");
29403            } else {
29404                self.write_space();
29405            }
29406            self.generate_expression(grouping_sets)?;
29407        }
29408        if let Some(totals) = &e.totals {
29409            self.write_space();
29410            self.write_keyword("WITH TOTALS");
29411            self.generate_expression(totals)?;
29412        }
29413        Ok(())
29414    }
29415
29416    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29417        // GROUP BY expressions
29418        self.write_keyword("GROUP BY");
29419        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29420        match e.all {
29421            Some(true) => {
29422                self.write_space();
29423                self.write_keyword("ALL");
29424            }
29425            Some(false) => {
29426                self.write_space();
29427                self.write_keyword("DISTINCT");
29428            }
29429            None => {}
29430        }
29431
29432        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29433        // These are represented as Cube/Rollup expressions with empty expressions at the end
29434        let mut trailing_cube = false;
29435        let mut trailing_rollup = false;
29436        let mut regular_expressions: Vec<&Expression> = Vec::new();
29437
29438        for expr in &e.expressions {
29439            match expr {
29440                Expression::Cube(c) if c.expressions.is_empty() => {
29441                    trailing_cube = true;
29442                }
29443                Expression::Rollup(r) if r.expressions.is_empty() => {
29444                    trailing_rollup = true;
29445                }
29446                _ => {
29447                    regular_expressions.push(expr);
29448                }
29449            }
29450        }
29451
29452        // In pretty mode, put columns on separate lines
29453        if self.config.pretty {
29454            self.write_newline();
29455            self.indent_level += 1;
29456            for (i, expr) in regular_expressions.iter().enumerate() {
29457                if i > 0 {
29458                    self.write(",");
29459                    self.write_newline();
29460                }
29461                self.write_indent();
29462                self.generate_expression(expr)?;
29463            }
29464            self.indent_level -= 1;
29465        } else {
29466            self.write_space();
29467            for (i, expr) in regular_expressions.iter().enumerate() {
29468                if i > 0 {
29469                    self.write(", ");
29470                }
29471                self.generate_expression(expr)?;
29472            }
29473        }
29474
29475        // Output trailing WITH CUBE or WITH ROLLUP
29476        if trailing_cube {
29477            self.write_space();
29478            self.write_keyword("WITH CUBE");
29479        } else if trailing_rollup {
29480            self.write_space();
29481            self.write_keyword("WITH ROLLUP");
29482        }
29483
29484        // ClickHouse: WITH TOTALS
29485        if e.totals {
29486            self.write_space();
29487            self.write_keyword("WITH TOTALS");
29488        }
29489
29490        Ok(())
29491    }
29492
29493    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
29494        // GROUPING(col1, col2, ...)
29495        self.write_keyword("GROUPING");
29496        self.write("(");
29497        for (i, expr) in e.expressions.iter().enumerate() {
29498            if i > 0 {
29499                self.write(", ");
29500            }
29501            self.generate_expression(expr)?;
29502        }
29503        self.write(")");
29504        Ok(())
29505    }
29506
29507    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
29508        // GROUPING_ID(col1, col2, ...)
29509        self.write_keyword("GROUPING_ID");
29510        self.write("(");
29511        for (i, expr) in e.expressions.iter().enumerate() {
29512            if i > 0 {
29513                self.write(", ");
29514            }
29515            self.generate_expression(expr)?;
29516        }
29517        self.write(")");
29518        Ok(())
29519    }
29520
29521    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
29522        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
29523        self.write_keyword("GROUPING SETS");
29524        self.write(" (");
29525        for (i, expr) in e.expressions.iter().enumerate() {
29526            if i > 0 {
29527                self.write(", ");
29528            }
29529            self.generate_expression(expr)?;
29530        }
29531        self.write(")");
29532        Ok(())
29533    }
29534
29535    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
29536        // HASH_AGG(this, expressions...)
29537        self.write_keyword("HASH_AGG");
29538        self.write("(");
29539        self.generate_expression(&e.this)?;
29540        for expr in &e.expressions {
29541            self.write(", ");
29542            self.generate_expression(expr)?;
29543        }
29544        self.write(")");
29545        Ok(())
29546    }
29547
29548    fn generate_having(&mut self, e: &Having) -> Result<()> {
29549        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
29550        self.write_keyword("HAVING");
29551        self.write_space();
29552        self.generate_expression(&e.this)?;
29553        Ok(())
29554    }
29555
29556    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
29557        // Python: this HAVING MAX|MIN expression
29558        self.generate_expression(&e.this)?;
29559        self.write_space();
29560        self.write_keyword("HAVING");
29561        self.write_space();
29562        if e.max.is_some() {
29563            self.write_keyword("MAX");
29564        } else {
29565            self.write_keyword("MIN");
29566        }
29567        self.write_space();
29568        self.generate_expression(&e.expression)?;
29569        Ok(())
29570    }
29571
29572    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29573        use crate::dialects::DialectType;
29574        // DuckDB: convert dollar-tagged strings to single-quoted
29575        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29576            // Extract the string content and output as single-quoted
29577            if let Expression::Literal(ref lit) = *e.this {
29578                if let Literal::String(ref s) = lit.as_ref() {
29579                    return self.generate_string_literal(s);
29580                }
29581            }
29582        }
29583        // PostgreSQL: preserve dollar-quoting
29584        if matches!(
29585            self.config.dialect,
29586            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29587        ) {
29588            self.write("$");
29589            if let Some(tag) = &e.tag {
29590                self.generate_expression(tag)?;
29591            }
29592            self.write("$");
29593            self.generate_expression(&e.this)?;
29594            self.write("$");
29595            if let Some(tag) = &e.tag {
29596                self.generate_expression(tag)?;
29597            }
29598            self.write("$");
29599            return Ok(());
29600        }
29601        // Default: output as dollar-tagged
29602        self.write("$");
29603        if let Some(tag) = &e.tag {
29604            self.generate_expression(tag)?;
29605        }
29606        self.write("$");
29607        self.generate_expression(&e.this)?;
29608        self.write("$");
29609        if let Some(tag) = &e.tag {
29610            self.generate_expression(tag)?;
29611        }
29612        self.write("$");
29613        Ok(())
29614    }
29615
29616    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29617        // HEX_ENCODE(this)
29618        self.write_keyword("HEX_ENCODE");
29619        self.write("(");
29620        self.generate_expression(&e.this)?;
29621        self.write(")");
29622        Ok(())
29623    }
29624
29625    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29626        // Python: this (kind => expression)
29627        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29628        match e.this.as_ref() {
29629            Expression::Identifier(id) => self.write(&id.name),
29630            other => self.generate_expression(other)?,
29631        }
29632        self.write(" (");
29633        self.write(&e.kind);
29634        self.write(" => ");
29635        self.generate_expression(&e.expression)?;
29636        self.write(")");
29637        Ok(())
29638    }
29639
29640    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29641        // HLL(this, expressions...)
29642        self.write_keyword("HLL");
29643        self.write("(");
29644        self.generate_expression(&e.this)?;
29645        for expr in &e.expressions {
29646            self.write(", ");
29647            self.generate_expression(expr)?;
29648        }
29649        self.write(")");
29650        Ok(())
29651    }
29652
29653    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29654        // Python: IN|OUT|IN OUT
29655        if e.input_.is_some() && e.output.is_some() {
29656            self.write_keyword("IN OUT");
29657        } else if e.input_.is_some() {
29658            self.write_keyword("IN");
29659        } else if e.output.is_some() {
29660            self.write_keyword("OUT");
29661        }
29662        Ok(())
29663    }
29664
29665    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29666        // Python: INCLUDE this [column_def] [AS alias]
29667        self.write_keyword("INCLUDE");
29668        self.write_space();
29669        self.generate_expression(&e.this)?;
29670        if let Some(column_def) = &e.column_def {
29671            self.write_space();
29672            self.generate_expression(column_def)?;
29673        }
29674        if let Some(alias) = &e.alias {
29675            self.write_space();
29676            self.write_keyword("AS");
29677            self.write_space();
29678            self.write(alias);
29679        }
29680        Ok(())
29681    }
29682
29683    fn generate_index(&mut self, e: &Index) -> Result<()> {
29684        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29685        if e.unique {
29686            self.write_keyword("UNIQUE");
29687            self.write_space();
29688        }
29689        if e.primary.is_some() {
29690            self.write_keyword("PRIMARY");
29691            self.write_space();
29692        }
29693        if e.amp.is_some() {
29694            self.write_keyword("AMP");
29695            self.write_space();
29696        }
29697        if e.table.is_none() {
29698            self.write_keyword("INDEX");
29699            self.write_space();
29700        }
29701        if let Some(name) = &e.this {
29702            self.generate_expression(name)?;
29703            self.write_space();
29704        }
29705        if let Some(table) = &e.table {
29706            self.write_keyword("ON");
29707            self.write_space();
29708            self.generate_expression(table)?;
29709        }
29710        if !e.params.is_empty() {
29711            self.write("(");
29712            for (i, param) in e.params.iter().enumerate() {
29713                if i > 0 {
29714                    self.write(", ");
29715                }
29716                self.generate_expression(param)?;
29717            }
29718            self.write(")");
29719        }
29720        Ok(())
29721    }
29722
29723    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29724        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29725        if let Some(kind) = &e.kind {
29726            self.write(kind);
29727            self.write_space();
29728        }
29729        self.write_keyword("INDEX");
29730        if let Some(this) = &e.this {
29731            self.write_space();
29732            self.generate_expression(this)?;
29733        }
29734        if let Some(index_type) = &e.index_type {
29735            self.write_space();
29736            self.write_keyword("USING");
29737            self.write_space();
29738            self.generate_expression(index_type)?;
29739        }
29740        if !e.expressions.is_empty() {
29741            self.write(" (");
29742            for (i, expr) in e.expressions.iter().enumerate() {
29743                if i > 0 {
29744                    self.write(", ");
29745                }
29746                self.generate_expression(expr)?;
29747            }
29748            self.write(")");
29749        }
29750        for opt in &e.options {
29751            self.write_space();
29752            self.generate_expression(opt)?;
29753        }
29754        Ok(())
29755    }
29756
29757    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29758        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29759        if let Some(key_block_size) = &e.key_block_size {
29760            self.write_keyword("KEY_BLOCK_SIZE");
29761            self.write(" = ");
29762            self.generate_expression(key_block_size)?;
29763        } else if let Some(using) = &e.using {
29764            self.write_keyword("USING");
29765            self.write_space();
29766            self.generate_expression(using)?;
29767        } else if let Some(parser) = &e.parser {
29768            self.write_keyword("WITH PARSER");
29769            self.write_space();
29770            self.generate_expression(parser)?;
29771        } else if let Some(comment) = &e.comment {
29772            self.write_keyword("COMMENT");
29773            self.write_space();
29774            self.generate_expression(comment)?;
29775        } else if let Some(visible) = &e.visible {
29776            self.generate_expression(visible)?;
29777        } else if let Some(engine_attr) = &e.engine_attr {
29778            self.write_keyword("ENGINE_ATTRIBUTE");
29779            self.write(" = ");
29780            self.generate_expression(engine_attr)?;
29781        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29782            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29783            self.write(" = ");
29784            self.generate_expression(secondary_engine_attr)?;
29785        }
29786        Ok(())
29787    }
29788
29789    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29790        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29791        if let Some(using) = &e.using {
29792            self.write_keyword("USING");
29793            self.write_space();
29794            self.generate_expression(using)?;
29795        }
29796        if !e.columns.is_empty() {
29797            self.write("(");
29798            for (i, col) in e.columns.iter().enumerate() {
29799                if i > 0 {
29800                    self.write(", ");
29801                }
29802                self.generate_expression(col)?;
29803            }
29804            self.write(")");
29805        }
29806        if let Some(partition_by) = &e.partition_by {
29807            self.write_space();
29808            self.write_keyword("PARTITION BY");
29809            self.write_space();
29810            self.generate_expression(partition_by)?;
29811        }
29812        if let Some(where_) = &e.where_ {
29813            self.write_space();
29814            self.generate_expression(where_)?;
29815        }
29816        if let Some(include) = &e.include {
29817            self.write_space();
29818            self.write_keyword("INCLUDE");
29819            self.write(" (");
29820            self.generate_expression(include)?;
29821            self.write(")");
29822        }
29823        if let Some(with_storage) = &e.with_storage {
29824            self.write_space();
29825            self.write_keyword("WITH");
29826            self.write(" (");
29827            self.generate_expression(with_storage)?;
29828            self.write(")");
29829        }
29830        if let Some(tablespace) = &e.tablespace {
29831            self.write_space();
29832            self.write_keyword("USING INDEX TABLESPACE");
29833            self.write_space();
29834            self.generate_expression(tablespace)?;
29835        }
29836        Ok(())
29837    }
29838
29839    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29840        // Python: this INDEX [FOR target] (expressions)
29841        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29842        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29843        if let Expression::Identifier(id) = &*e.this {
29844            self.write_keyword(&id.name);
29845        } else {
29846            self.generate_expression(&e.this)?;
29847        }
29848        self.write_space();
29849        self.write_keyword("INDEX");
29850        if let Some(target) = &e.target {
29851            self.write_space();
29852            self.write_keyword("FOR");
29853            self.write_space();
29854            if let Expression::Identifier(id) = &**target {
29855                self.write_keyword(&id.name);
29856            } else {
29857                self.generate_expression(target)?;
29858            }
29859        }
29860        // Always output parentheses (even if empty, e.g. USE INDEX ())
29861        self.write(" (");
29862        for (i, expr) in e.expressions.iter().enumerate() {
29863            if i > 0 {
29864                self.write(", ");
29865            }
29866            self.generate_expression(expr)?;
29867        }
29868        self.write(")");
29869        Ok(())
29870    }
29871
29872    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29873        // INHERITS (table1, table2, ...)
29874        self.write_keyword("INHERITS");
29875        self.write(" (");
29876        for (i, expr) in e.expressions.iter().enumerate() {
29877            if i > 0 {
29878                self.write(", ");
29879            }
29880            self.generate_expression(expr)?;
29881        }
29882        self.write(")");
29883        Ok(())
29884    }
29885
29886    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29887        // INPUT(model)
29888        self.write_keyword("INPUT");
29889        self.write("(");
29890        self.generate_expression(&e.this)?;
29891        self.write(")");
29892        Ok(())
29893    }
29894
29895    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29896        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29897        if let Some(input_format) = &e.input_format {
29898            self.write_keyword("INPUTFORMAT");
29899            self.write_space();
29900            self.generate_expression(input_format)?;
29901        }
29902        if let Some(output_format) = &e.output_format {
29903            if e.input_format.is_some() {
29904                self.write(" ");
29905            }
29906            self.write_keyword("OUTPUTFORMAT");
29907            self.write_space();
29908            self.generate_expression(output_format)?;
29909        }
29910        Ok(())
29911    }
29912
29913    fn generate_install(&mut self, e: &Install) -> Result<()> {
29914        // [FORCE] INSTALL extension [FROM source]
29915        if e.force.is_some() {
29916            self.write_keyword("FORCE");
29917            self.write_space();
29918        }
29919        self.write_keyword("INSTALL");
29920        self.write_space();
29921        self.generate_expression(&e.this)?;
29922        if let Some(from) = &e.from_ {
29923            self.write_space();
29924            self.write_keyword("FROM");
29925            self.write_space();
29926            self.generate_expression(from)?;
29927        }
29928        Ok(())
29929    }
29930
29931    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29932        // INTERVAL 'expression' unit
29933        self.write_keyword("INTERVAL");
29934        self.write_space();
29935        // When a unit is specified and the expression is a number,
29936        self.generate_expression(&e.expression)?;
29937        if let Some(unit) = &e.unit {
29938            self.write_space();
29939            self.write(unit);
29940        }
29941        Ok(())
29942    }
29943
29944    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29945        // unit TO unit (e.g., HOUR TO SECOND)
29946        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29947        self.write_space();
29948        self.write_keyword("TO");
29949        self.write_space();
29950        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29951        Ok(())
29952    }
29953
29954    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29955        // INTO [TEMPORARY|UNLOGGED] table
29956        self.write_keyword("INTO");
29957        if e.temporary {
29958            self.write_keyword(" TEMPORARY");
29959        }
29960        if e.unlogged.is_some() {
29961            self.write_keyword(" UNLOGGED");
29962        }
29963        if let Some(this) = &e.this {
29964            self.write_space();
29965            self.generate_expression(this)?;
29966        }
29967        if !e.expressions.is_empty() {
29968            self.write(" (");
29969            for (i, expr) in e.expressions.iter().enumerate() {
29970                if i > 0 {
29971                    self.write(", ");
29972                }
29973                self.generate_expression(expr)?;
29974            }
29975            self.write(")");
29976        }
29977        Ok(())
29978    }
29979
29980    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29981        // Python: this expression (e.g., _utf8 'string')
29982        self.generate_expression(&e.this)?;
29983        self.write_space();
29984        self.generate_expression(&e.expression)?;
29985        Ok(())
29986    }
29987
29988    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29989        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29990        self.write_keyword("WITH");
29991        if e.no.is_some() {
29992            self.write_keyword(" NO");
29993        }
29994        if e.concurrent.is_some() {
29995            self.write_keyword(" CONCURRENT");
29996        }
29997        self.write_keyword(" ISOLATED LOADING");
29998        if let Some(target) = &e.target {
29999            self.write_space();
30000            self.generate_expression(target)?;
30001        }
30002        Ok(())
30003    }
30004
30005    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30006        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30007        self.write_keyword("JSON");
30008        if let Some(this) = &e.this {
30009            self.write_space();
30010            self.generate_expression(this)?;
30011        }
30012        if let Some(with_) = &e.with_ {
30013            // Check if it's a truthy boolean
30014            if let Expression::Boolean(b) = with_.as_ref() {
30015                if b.value {
30016                    self.write_keyword(" WITH");
30017                } else {
30018                    self.write_keyword(" WITHOUT");
30019                }
30020            }
30021        }
30022        if e.unique {
30023            self.write_keyword(" UNIQUE KEYS");
30024        }
30025        Ok(())
30026    }
30027
30028    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30029        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30030        self.write_keyword("JSON_ARRAY");
30031        self.write("(");
30032        for (i, expr) in e.expressions.iter().enumerate() {
30033            if i > 0 {
30034                self.write(", ");
30035            }
30036            self.generate_expression(expr)?;
30037        }
30038        if let Some(null_handling) = &e.null_handling {
30039            self.write_space();
30040            self.generate_expression(null_handling)?;
30041        }
30042        if let Some(return_type) = &e.return_type {
30043            self.write_space();
30044            self.write_keyword("RETURNING");
30045            self.write_space();
30046            self.generate_expression(return_type)?;
30047        }
30048        if e.strict.is_some() {
30049            self.write_space();
30050            self.write_keyword("STRICT");
30051        }
30052        self.write(")");
30053        Ok(())
30054    }
30055
30056    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30057        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30058        self.write_keyword("JSON_ARRAYAGG");
30059        self.write("(");
30060        self.generate_expression(&e.this)?;
30061        if let Some(order) = &e.order {
30062            self.write_space();
30063            // Order is stored as an OrderBy expression
30064            if let Expression::OrderBy(ob) = order.as_ref() {
30065                self.write_keyword("ORDER BY");
30066                self.write_space();
30067                for (i, ord) in ob.expressions.iter().enumerate() {
30068                    if i > 0 {
30069                        self.write(", ");
30070                    }
30071                    self.generate_ordered(ord)?;
30072                }
30073            } else {
30074                // Fallback: generate the expression directly
30075                self.generate_expression(order)?;
30076            }
30077        }
30078        if let Some(null_handling) = &e.null_handling {
30079            self.write_space();
30080            self.generate_expression(null_handling)?;
30081        }
30082        if let Some(return_type) = &e.return_type {
30083            self.write_space();
30084            self.write_keyword("RETURNING");
30085            self.write_space();
30086            self.generate_expression(return_type)?;
30087        }
30088        if e.strict.is_some() {
30089            self.write_space();
30090            self.write_keyword("STRICT");
30091        }
30092        self.write(")");
30093        Ok(())
30094    }
30095
30096    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30097        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30098        self.write_keyword("JSON_OBJECTAGG");
30099        self.write("(");
30100        for (i, expr) in e.expressions.iter().enumerate() {
30101            if i > 0 {
30102                self.write(", ");
30103            }
30104            self.generate_expression(expr)?;
30105        }
30106        if let Some(null_handling) = &e.null_handling {
30107            self.write_space();
30108            self.generate_expression(null_handling)?;
30109        }
30110        if let Some(unique_keys) = &e.unique_keys {
30111            self.write_space();
30112            if let Expression::Boolean(b) = unique_keys.as_ref() {
30113                if b.value {
30114                    self.write_keyword("WITH UNIQUE KEYS");
30115                } else {
30116                    self.write_keyword("WITHOUT UNIQUE KEYS");
30117                }
30118            }
30119        }
30120        if let Some(return_type) = &e.return_type {
30121            self.write_space();
30122            self.write_keyword("RETURNING");
30123            self.write_space();
30124            self.generate_expression(return_type)?;
30125        }
30126        self.write(")");
30127        Ok(())
30128    }
30129
30130    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30131        // JSON_ARRAY_APPEND(this, path, value, ...)
30132        self.write_keyword("JSON_ARRAY_APPEND");
30133        self.write("(");
30134        self.generate_expression(&e.this)?;
30135        for expr in &e.expressions {
30136            self.write(", ");
30137            self.generate_expression(expr)?;
30138        }
30139        self.write(")");
30140        Ok(())
30141    }
30142
30143    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30144        // JSON_ARRAY_CONTAINS(this, expression)
30145        self.write_keyword("JSON_ARRAY_CONTAINS");
30146        self.write("(");
30147        self.generate_expression(&e.this)?;
30148        self.write(", ");
30149        self.generate_expression(&e.expression)?;
30150        self.write(")");
30151        Ok(())
30152    }
30153
30154    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30155        // JSON_ARRAY_INSERT(this, path, value, ...)
30156        self.write_keyword("JSON_ARRAY_INSERT");
30157        self.write("(");
30158        self.generate_expression(&e.this)?;
30159        for expr in &e.expressions {
30160            self.write(", ");
30161            self.generate_expression(expr)?;
30162        }
30163        self.write(")");
30164        Ok(())
30165    }
30166
30167    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30168        // JSONB_EXISTS(this, path)
30169        self.write_keyword("JSONB_EXISTS");
30170        self.write("(");
30171        self.generate_expression(&e.this)?;
30172        if let Some(path) = &e.path {
30173            self.write(", ");
30174            self.generate_expression(path)?;
30175        }
30176        self.write(")");
30177        Ok(())
30178    }
30179
30180    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30181        // JSONB_EXTRACT_SCALAR(this, expression)
30182        self.write_keyword("JSONB_EXTRACT_SCALAR");
30183        self.write("(");
30184        self.generate_expression(&e.this)?;
30185        self.write(", ");
30186        self.generate_expression(&e.expression)?;
30187        self.write(")");
30188        Ok(())
30189    }
30190
30191    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30192        // JSONB_OBJECT_AGG(this, expression)
30193        self.write_keyword("JSONB_OBJECT_AGG");
30194        self.write("(");
30195        self.generate_expression(&e.this)?;
30196        self.write(", ");
30197        self.generate_expression(&e.expression)?;
30198        self.write(")");
30199        Ok(())
30200    }
30201
30202    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30203        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30204        if let Some(nested_schema) = &e.nested_schema {
30205            self.write_keyword("NESTED");
30206            if let Some(path) = &e.path {
30207                self.write_space();
30208                self.write_keyword("PATH");
30209                self.write_space();
30210                self.generate_expression(path)?;
30211            }
30212            self.write_space();
30213            self.generate_expression(nested_schema)?;
30214        } else {
30215            if let Some(this) = &e.this {
30216                self.generate_expression(this)?;
30217            }
30218            if let Some(kind) = &e.kind {
30219                self.write_space();
30220                self.write(kind);
30221            }
30222            if e.format_json {
30223                self.write_space();
30224                self.write_keyword("FORMAT JSON");
30225            }
30226            if let Some(path) = &e.path {
30227                self.write_space();
30228                self.write_keyword("PATH");
30229                self.write_space();
30230                self.generate_expression(path)?;
30231            }
30232            if e.ordinality.is_some() {
30233                self.write_keyword(" FOR ORDINALITY");
30234            }
30235        }
30236        Ok(())
30237    }
30238
30239    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30240        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30241        self.write_keyword("JSON_EXISTS");
30242        self.write("(");
30243        self.generate_expression(&e.this)?;
30244        if let Some(path) = &e.path {
30245            self.write(", ");
30246            self.generate_expression(path)?;
30247        }
30248        if let Some(passing) = &e.passing {
30249            self.write_space();
30250            self.write_keyword("PASSING");
30251            self.write_space();
30252            self.generate_expression(passing)?;
30253        }
30254        if let Some(on_condition) = &e.on_condition {
30255            self.write_space();
30256            self.generate_expression(on_condition)?;
30257        }
30258        self.write(")");
30259        Ok(())
30260    }
30261
30262    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30263        self.generate_expression(&e.this)?;
30264        self.write(".:");
30265        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30266        // wrap the entire type string in double quotes.
30267        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30268        if Self::data_type_has_nested_expressions(&e.to) {
30269            // Generate the data type to a temporary string buffer, then wrap in quotes
30270            let saved = std::mem::take(&mut self.output);
30271            self.generate_data_type(&e.to)?;
30272            let type_sql = std::mem::replace(&mut self.output, saved);
30273            self.write("\"");
30274            self.write(&type_sql);
30275            self.write("\"");
30276        } else {
30277            self.generate_data_type(&e.to)?;
30278        }
30279        Ok(())
30280    }
30281
30282    /// Check if a DataType has nested type expressions (sub-types).
30283    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30284    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30285        matches!(
30286            dt,
30287            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30288        )
30289    }
30290
30291    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30292        // JSON_EXTRACT_ARRAY(this, expression)
30293        self.write_keyword("JSON_EXTRACT_ARRAY");
30294        self.write("(");
30295        self.generate_expression(&e.this)?;
30296        if let Some(expr) = &e.expression {
30297            self.write(", ");
30298            self.generate_expression(expr)?;
30299        }
30300        self.write(")");
30301        Ok(())
30302    }
30303
30304    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30305        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30306        if let Some(option) = &e.option {
30307            self.generate_expression(option)?;
30308            self.write_space();
30309        }
30310        self.write_keyword("QUOTES");
30311        if e.scalar.is_some() {
30312            self.write_keyword(" SCALAR_ONLY");
30313        }
30314        Ok(())
30315    }
30316
30317    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30318        // JSON_EXTRACT_SCALAR(this, expression)
30319        self.write_keyword("JSON_EXTRACT_SCALAR");
30320        self.write("(");
30321        self.generate_expression(&e.this)?;
30322        self.write(", ");
30323        self.generate_expression(&e.expression)?;
30324        self.write(")");
30325        Ok(())
30326    }
30327
30328    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30329        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30330        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30331        // Otherwise output JSON_EXTRACT(this, expression)
30332        if e.variant_extract.is_some() {
30333            use crate::dialects::DialectType;
30334            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30335                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30336                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30337                // bracket notation: c:["x-y"] instead of c:x-y
30338                self.generate_expression(&e.this)?;
30339                self.write(":");
30340                match e.expression.as_ref() {
30341                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30342                        let Literal::String(s) = lit.as_ref() else {
30343                            unreachable!()
30344                        };
30345                        self.write_databricks_json_path(s);
30346                    }
30347                    _ => {
30348                        // Fallback: generate as-is (shouldn't happen in typical cases)
30349                        self.generate_expression(&e.expression)?;
30350                    }
30351                }
30352            } else {
30353                // Snowflake and others: use GET_PATH(col, 'path')
30354                self.write_keyword("GET_PATH");
30355                self.write("(");
30356                self.generate_expression(&e.this)?;
30357                self.write(", ");
30358                self.generate_expression(&e.expression)?;
30359                self.write(")");
30360            }
30361        } else {
30362            self.write_keyword("JSON_EXTRACT");
30363            self.write("(");
30364            self.generate_expression(&e.this)?;
30365            self.write(", ");
30366            self.generate_expression(&e.expression)?;
30367            for expr in &e.expressions {
30368                self.write(", ");
30369                self.generate_expression(expr)?;
30370            }
30371            self.write(")");
30372        }
30373        Ok(())
30374    }
30375
30376    /// Write a Databricks JSON colon-path, using bracket notation for keys
30377    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30378    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30379    fn write_databricks_json_path(&mut self, path: &str) {
30380        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30381        // it was already formatted by the parser - output as-is
30382        if path.starts_with("[\"") || path.starts_with("['") {
30383            self.write(path);
30384            return;
30385        }
30386        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30387        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30388        // e.g., "x-y" -> ["x-y"]
30389        let mut first = true;
30390        for segment in path.split('.') {
30391            if !first {
30392                self.write(".");
30393            }
30394            first = false;
30395            // Check if there's a bracket subscript in this segment: "items[0]"
30396            if let Some(bracket_pos) = segment.find('[') {
30397                let key = &segment[..bracket_pos];
30398                let subscript = &segment[bracket_pos..];
30399                if key.is_empty() {
30400                    // Bracket notation at start of segment (e.g., already formatted)
30401                    self.write(segment);
30402                } else if Self::is_safe_json_path_key(key) {
30403                    self.write(key);
30404                    self.write(subscript);
30405                } else {
30406                    self.write("[\"");
30407                    self.write(key);
30408                    self.write("\"]");
30409                    self.write(subscript);
30410                }
30411            } else if Self::is_safe_json_path_key(segment) {
30412                self.write(segment);
30413            } else {
30414                self.write("[\"");
30415                self.write(segment);
30416                self.write("\"]");
30417            }
30418        }
30419    }
30420
30421    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30422    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30423    fn is_safe_json_path_key(key: &str) -> bool {
30424        if key.is_empty() {
30425            return false;
30426        }
30427        let mut chars = key.chars();
30428        let first = chars.next().unwrap();
30429        if first != '_' && !first.is_ascii_alphabetic() {
30430            return false;
30431        }
30432        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30433    }
30434
30435    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30436        // Output: {expr} FORMAT JSON
30437        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30438        if let Some(this) = &e.this {
30439            self.generate_expression(this)?;
30440            self.write_space();
30441        }
30442        self.write_keyword("FORMAT JSON");
30443        Ok(())
30444    }
30445
30446    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30447        // key: value (for JSON objects)
30448        self.generate_expression(&e.this)?;
30449        self.write(": ");
30450        self.generate_expression(&e.expression)?;
30451        Ok(())
30452    }
30453
30454    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30455        // JSON_KEYS(this, expression, expressions...)
30456        self.write_keyword("JSON_KEYS");
30457        self.write("(");
30458        self.generate_expression(&e.this)?;
30459        if let Some(expr) = &e.expression {
30460            self.write(", ");
30461            self.generate_expression(expr)?;
30462        }
30463        for expr in &e.expressions {
30464            self.write(", ");
30465            self.generate_expression(expr)?;
30466        }
30467        self.write(")");
30468        Ok(())
30469    }
30470
30471    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
30472        // JSON_KEYS(this, expression)
30473        self.write_keyword("JSON_KEYS");
30474        self.write("(");
30475        self.generate_expression(&e.this)?;
30476        if let Some(expr) = &e.expression {
30477            self.write(", ");
30478            self.generate_expression(expr)?;
30479        }
30480        self.write(")");
30481        Ok(())
30482    }
30483
30484    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
30485        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
30486        // The path components are concatenated without spaces
30487        let mut path_str = String::new();
30488        for expr in &e.expressions {
30489            match expr {
30490                Expression::JSONPathRoot(_) => {
30491                    path_str.push('$');
30492                }
30493                Expression::JSONPathKey(k) => {
30494                    // .key or ."key" (quote if key has special characters)
30495                    if let Expression::Literal(lit) = k.this.as_ref() {
30496                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
30497                            path_str.push('.');
30498                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
30499                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
30500                            if needs_quoting {
30501                                path_str.push('"');
30502                                path_str.push_str(s);
30503                                path_str.push('"');
30504                            } else {
30505                                path_str.push_str(s);
30506                            }
30507                        }
30508                    }
30509                }
30510                Expression::JSONPathSubscript(s) => {
30511                    // [index]
30512                    if let Expression::Literal(lit) = s.this.as_ref() {
30513                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
30514                            path_str.push('[');
30515                            path_str.push_str(n);
30516                            path_str.push(']');
30517                        }
30518                    }
30519                }
30520                _ => {
30521                    // For other path parts, try to generate them
30522                    let mut temp_gen = Self::with_arc_config(self.config.clone());
30523                    temp_gen.generate_expression(expr)?;
30524                    path_str.push_str(&temp_gen.output);
30525                }
30526            }
30527        }
30528        // Output as quoted string
30529        self.write("'");
30530        self.write(&path_str);
30531        self.write("'");
30532        Ok(())
30533    }
30534
30535    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
30536        // JSON path filter: ?(predicate)
30537        self.write("?(");
30538        self.generate_expression(&e.this)?;
30539        self.write(")");
30540        Ok(())
30541    }
30542
30543    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
30544        // JSON path key: .key or ["key"]
30545        self.write(".");
30546        self.generate_expression(&e.this)?;
30547        Ok(())
30548    }
30549
30550    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
30551        // JSON path recursive descent: ..
30552        self.write("..");
30553        if let Some(this) = &e.this {
30554            self.generate_expression(this)?;
30555        }
30556        Ok(())
30557    }
30558
30559    fn generate_json_path_root(&mut self) -> Result<()> {
30560        // JSON path root: $
30561        self.write("$");
30562        Ok(())
30563    }
30564
30565    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
30566        // JSON path script: (expression)
30567        self.write("(");
30568        self.generate_expression(&e.this)?;
30569        self.write(")");
30570        Ok(())
30571    }
30572
30573    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30574        // JSON path selector: *
30575        self.generate_expression(&e.this)?;
30576        Ok(())
30577    }
30578
30579    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30580        // JSON path slice: [start:end:step]
30581        self.write("[");
30582        if let Some(start) = &e.start {
30583            self.generate_expression(start)?;
30584        }
30585        self.write(":");
30586        if let Some(end) = &e.end {
30587            self.generate_expression(end)?;
30588        }
30589        if let Some(step) = &e.step {
30590            self.write(":");
30591            self.generate_expression(step)?;
30592        }
30593        self.write("]");
30594        Ok(())
30595    }
30596
30597    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30598        // JSON path subscript: [index] or [*]
30599        self.write("[");
30600        self.generate_expression(&e.this)?;
30601        self.write("]");
30602        Ok(())
30603    }
30604
30605    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30606        // JSON path union: [key1, key2, ...]
30607        self.write("[");
30608        for (i, expr) in e.expressions.iter().enumerate() {
30609            if i > 0 {
30610                self.write(", ");
30611            }
30612            self.generate_expression(expr)?;
30613        }
30614        self.write("]");
30615        Ok(())
30616    }
30617
30618    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30619        // JSON_REMOVE(this, path1, path2, ...)
30620        self.write_keyword("JSON_REMOVE");
30621        self.write("(");
30622        self.generate_expression(&e.this)?;
30623        for expr in &e.expressions {
30624            self.write(", ");
30625            self.generate_expression(expr)?;
30626        }
30627        self.write(")");
30628        Ok(())
30629    }
30630
30631    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30632        // COLUMNS(col1 type, col2 type, ...)
30633        // When pretty printing and content is too wide, format with each column on a separate line
30634        self.write_keyword("COLUMNS");
30635        self.write("(");
30636
30637        if self.config.pretty && !e.expressions.is_empty() {
30638            // First, generate all expressions into strings to check width
30639            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30640            for expr in &e.expressions {
30641                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30642                temp_gen.generate_expression(expr)?;
30643                expr_strings.push(temp_gen.output);
30644            }
30645
30646            // Check if total width exceeds max_text_width
30647            if self.too_wide(&expr_strings) {
30648                // Pretty print: each column on its own line
30649                self.write_newline();
30650                self.indent_level += 1;
30651                for (i, expr_str) in expr_strings.iter().enumerate() {
30652                    if i > 0 {
30653                        self.write(",");
30654                        self.write_newline();
30655                    }
30656                    self.write_indent();
30657                    self.write(expr_str);
30658                }
30659                self.write_newline();
30660                self.indent_level -= 1;
30661                self.write_indent();
30662            } else {
30663                // Compact: all on one line
30664                for (i, expr_str) in expr_strings.iter().enumerate() {
30665                    if i > 0 {
30666                        self.write(", ");
30667                    }
30668                    self.write(expr_str);
30669                }
30670            }
30671        } else {
30672            // Non-pretty mode: compact format
30673            for (i, expr) in e.expressions.iter().enumerate() {
30674                if i > 0 {
30675                    self.write(", ");
30676                }
30677                self.generate_expression(expr)?;
30678            }
30679        }
30680        self.write(")");
30681        Ok(())
30682    }
30683
30684    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30685        // JSON_SET(this, path, value, ...)
30686        self.write_keyword("JSON_SET");
30687        self.write("(");
30688        self.generate_expression(&e.this)?;
30689        for expr in &e.expressions {
30690            self.write(", ");
30691            self.generate_expression(expr)?;
30692        }
30693        self.write(")");
30694        Ok(())
30695    }
30696
30697    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30698        // JSON_STRIP_NULLS(this, expression)
30699        self.write_keyword("JSON_STRIP_NULLS");
30700        self.write("(");
30701        self.generate_expression(&e.this)?;
30702        if let Some(expr) = &e.expression {
30703            self.write(", ");
30704            self.generate_expression(expr)?;
30705        }
30706        self.write(")");
30707        Ok(())
30708    }
30709
30710    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30711        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30712        self.write_keyword("JSON_TABLE");
30713        self.write("(");
30714        self.generate_expression(&e.this)?;
30715        if let Some(path) = &e.path {
30716            self.write(", ");
30717            self.generate_expression(path)?;
30718        }
30719        if let Some(error_handling) = &e.error_handling {
30720            self.write_space();
30721            self.generate_expression(error_handling)?;
30722        }
30723        if let Some(empty_handling) = &e.empty_handling {
30724            self.write_space();
30725            self.generate_expression(empty_handling)?;
30726        }
30727        if let Some(schema) = &e.schema {
30728            self.write_space();
30729            self.generate_expression(schema)?;
30730        }
30731        self.write(")");
30732        Ok(())
30733    }
30734
30735    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30736        // JSON_TYPE(this)
30737        self.write_keyword("JSON_TYPE");
30738        self.write("(");
30739        self.generate_expression(&e.this)?;
30740        self.write(")");
30741        Ok(())
30742    }
30743
30744    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30745        // JSON_VALUE(this, path RETURNING type ON condition)
30746        self.write_keyword("JSON_VALUE");
30747        self.write("(");
30748        self.generate_expression(&e.this)?;
30749        if let Some(path) = &e.path {
30750            self.write(", ");
30751            self.generate_expression(path)?;
30752        }
30753        if let Some(returning) = &e.returning {
30754            self.write_space();
30755            self.write_keyword("RETURNING");
30756            self.write_space();
30757            self.generate_expression(returning)?;
30758        }
30759        if let Some(on_condition) = &e.on_condition {
30760            self.write_space();
30761            self.generate_expression(on_condition)?;
30762        }
30763        self.write(")");
30764        Ok(())
30765    }
30766
30767    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30768        // JSON_VALUE_ARRAY(this)
30769        self.write_keyword("JSON_VALUE_ARRAY");
30770        self.write("(");
30771        self.generate_expression(&e.this)?;
30772        self.write(")");
30773        Ok(())
30774    }
30775
30776    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30777        // JAROWINKLER_SIMILARITY(str1, str2)
30778        self.write_keyword("JAROWINKLER_SIMILARITY");
30779        self.write("(");
30780        self.generate_expression(&e.this)?;
30781        self.write(", ");
30782        self.generate_expression(&e.expression)?;
30783        self.write(")");
30784        Ok(())
30785    }
30786
30787    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30788        // Python: this(expressions)
30789        self.generate_expression(&e.this)?;
30790        self.write("(");
30791        for (i, expr) in e.expressions.iter().enumerate() {
30792            if i > 0 {
30793                self.write(", ");
30794            }
30795            self.generate_expression(expr)?;
30796        }
30797        self.write(")");
30798        Ok(())
30799    }
30800
30801    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30802        // Python: {no}{local}{dual}{before}{after}JOURNAL
30803        if e.no.is_some() {
30804            self.write_keyword("NO ");
30805        }
30806        if let Some(local) = &e.local {
30807            self.generate_expression(local)?;
30808            self.write_space();
30809        }
30810        if e.dual.is_some() {
30811            self.write_keyword("DUAL ");
30812        }
30813        if e.before.is_some() {
30814            self.write_keyword("BEFORE ");
30815        }
30816        if e.after.is_some() {
30817            self.write_keyword("AFTER ");
30818        }
30819        self.write_keyword("JOURNAL");
30820        Ok(())
30821    }
30822
30823    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30824        // LANGUAGE language_name
30825        self.write_keyword("LANGUAGE");
30826        self.write_space();
30827        self.generate_expression(&e.this)?;
30828        Ok(())
30829    }
30830
30831    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30832        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30833        if e.view.is_some() {
30834            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30835            self.write_keyword("LATERAL VIEW");
30836            if e.outer.is_some() {
30837                self.write_space();
30838                self.write_keyword("OUTER");
30839            }
30840            self.write_space();
30841            self.generate_expression(&e.this)?;
30842            if let Some(alias) = &e.alias {
30843                self.write_space();
30844                self.write(alias);
30845            }
30846        } else {
30847            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30848            self.write_keyword("LATERAL");
30849            self.write_space();
30850            self.generate_expression(&e.this)?;
30851            if e.ordinality.is_some() {
30852                self.write_space();
30853                self.write_keyword("WITH ORDINALITY");
30854            }
30855            if let Some(alias) = &e.alias {
30856                self.write_space();
30857                self.write_keyword("AS");
30858                self.write_space();
30859                self.write(alias);
30860                if !e.column_aliases.is_empty() {
30861                    self.write("(");
30862                    for (i, col) in e.column_aliases.iter().enumerate() {
30863                        if i > 0 {
30864                            self.write(", ");
30865                        }
30866                        self.write(col);
30867                    }
30868                    self.write(")");
30869                }
30870            }
30871        }
30872        Ok(())
30873    }
30874
30875    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30876        // Python: LIKE this [options]
30877        self.write_keyword("LIKE");
30878        self.write_space();
30879        self.generate_expression(&e.this)?;
30880        for expr in &e.expressions {
30881            self.write_space();
30882            self.generate_expression(expr)?;
30883        }
30884        Ok(())
30885    }
30886
30887    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30888        self.write_keyword("LIMIT");
30889        self.write_space();
30890        self.write_limit_expr(&e.this)?;
30891        if e.percent {
30892            self.write_space();
30893            self.write_keyword("PERCENT");
30894        }
30895        // Emit any comments that were captured from before the LIMIT keyword
30896        for comment in &e.comments {
30897            self.write(" ");
30898            self.write_formatted_comment(comment);
30899        }
30900        Ok(())
30901    }
30902
30903    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30904        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30905        if e.percent.is_some() {
30906            self.write_keyword(" PERCENT");
30907        }
30908        if e.rows.is_some() {
30909            self.write_keyword(" ROWS");
30910        }
30911        if e.with_ties.is_some() {
30912            self.write_keyword(" WITH TIES");
30913        } else if e.rows.is_some() {
30914            self.write_keyword(" ONLY");
30915        }
30916        Ok(())
30917    }
30918
30919    fn generate_list(&mut self, e: &List) -> Result<()> {
30920        use crate::dialects::DialectType;
30921        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30922
30923        // Check if this is a subquery-based list (LIST(SELECT ...))
30924        if e.expressions.len() == 1 {
30925            if let Expression::Select(_) = &e.expressions[0] {
30926                self.write_keyword("LIST");
30927                self.write("(");
30928                self.generate_expression(&e.expressions[0])?;
30929                self.write(")");
30930                return Ok(());
30931            }
30932        }
30933
30934        // For Materialize, output as LIST[expr, expr, ...]
30935        if is_materialize {
30936            self.write_keyword("LIST");
30937            self.write("[");
30938            for (i, expr) in e.expressions.iter().enumerate() {
30939                if i > 0 {
30940                    self.write(", ");
30941                }
30942                self.generate_expression(expr)?;
30943            }
30944            self.write("]");
30945        } else {
30946            // For other dialects, output as LIST(expr, expr, ...)
30947            self.write_keyword("LIST");
30948            self.write("(");
30949            for (i, expr) in e.expressions.iter().enumerate() {
30950                if i > 0 {
30951                    self.write(", ");
30952                }
30953                self.generate_expression(expr)?;
30954            }
30955            self.write(")");
30956        }
30957        Ok(())
30958    }
30959
30960    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30961        // Check if this is a subquery-based map (MAP(SELECT ...))
30962        if let Expression::Select(_) = &*e.this {
30963            self.write_keyword("MAP");
30964            self.write("(");
30965            self.generate_expression(&e.this)?;
30966            self.write(")");
30967            return Ok(());
30968        }
30969
30970        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30971
30972        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30973        self.write_keyword("MAP");
30974        if is_duckdb {
30975            self.write(" {");
30976        } else {
30977            self.write("[");
30978        }
30979        if let Expression::Struct(s) = &*e.this {
30980            for (i, (_, expr)) in s.fields.iter().enumerate() {
30981                if i > 0 {
30982                    self.write(", ");
30983                }
30984                if let Expression::PropertyEQ(op) = expr {
30985                    self.generate_expression(&op.left)?;
30986                    if is_duckdb {
30987                        self.write(": ");
30988                    } else {
30989                        self.write(" => ");
30990                    }
30991                    self.generate_expression(&op.right)?;
30992                } else {
30993                    self.generate_expression(expr)?;
30994                }
30995            }
30996        }
30997        if is_duckdb {
30998            self.write("}");
30999        } else {
31000            self.write("]");
31001        }
31002        Ok(())
31003    }
31004
31005    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31006        // Python: LOCALTIME or LOCALTIME(precision)
31007        self.write_keyword("LOCALTIME");
31008        if let Some(precision) = &e.this {
31009            self.write("(");
31010            self.generate_expression(precision)?;
31011            self.write(")");
31012        }
31013        Ok(())
31014    }
31015
31016    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31017        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31018        self.write_keyword("LOCALTIMESTAMP");
31019        if let Some(precision) = &e.this {
31020            self.write("(");
31021            self.generate_expression(precision)?;
31022            self.write(")");
31023        }
31024        Ok(())
31025    }
31026
31027    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31028        // LOCATION 'path'
31029        self.write_keyword("LOCATION");
31030        self.write_space();
31031        self.generate_expression(&e.this)?;
31032        Ok(())
31033    }
31034
31035    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31036        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31037        if e.update.is_some() {
31038            if e.key.is_some() {
31039                self.write_keyword("FOR NO KEY UPDATE");
31040            } else {
31041                self.write_keyword("FOR UPDATE");
31042            }
31043        } else {
31044            if e.key.is_some() {
31045                self.write_keyword("FOR KEY SHARE");
31046            } else {
31047                self.write_keyword("FOR SHARE");
31048            }
31049        }
31050        if !e.expressions.is_empty() {
31051            self.write_keyword(" OF ");
31052            for (i, expr) in e.expressions.iter().enumerate() {
31053                if i > 0 {
31054                    self.write(", ");
31055                }
31056                self.generate_expression(expr)?;
31057            }
31058        }
31059        // Handle wait option following Python sqlglot convention:
31060        // - Boolean(true) -> NOWAIT
31061        // - Boolean(false) -> SKIP LOCKED
31062        // - Literal (number) -> WAIT n
31063        if let Some(wait) = &e.wait {
31064            match wait.as_ref() {
31065                Expression::Boolean(b) => {
31066                    if b.value {
31067                        self.write_keyword(" NOWAIT");
31068                    } else {
31069                        self.write_keyword(" SKIP LOCKED");
31070                    }
31071                }
31072                _ => {
31073                    // It's a literal (number), output WAIT n
31074                    self.write_keyword(" WAIT ");
31075                    self.generate_expression(wait)?;
31076                }
31077            }
31078        }
31079        Ok(())
31080    }
31081
31082    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31083        // LOCK property
31084        self.write_keyword("LOCK");
31085        self.write_space();
31086        self.generate_expression(&e.this)?;
31087        Ok(())
31088    }
31089
31090    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31091        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31092        self.write_keyword("LOCKING");
31093        self.write_space();
31094        self.write(&e.kind);
31095        if let Some(this) = &e.this {
31096            self.write_space();
31097            self.generate_expression(this)?;
31098        }
31099        if let Some(for_or_in) = &e.for_or_in {
31100            self.write_space();
31101            self.generate_expression(for_or_in)?;
31102        }
31103        if let Some(lock_type) = &e.lock_type {
31104            self.write_space();
31105            self.generate_expression(lock_type)?;
31106        }
31107        if e.override_.is_some() {
31108            self.write_keyword(" OVERRIDE");
31109        }
31110        Ok(())
31111    }
31112
31113    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31114        // this expression
31115        self.generate_expression(&e.this)?;
31116        self.write_space();
31117        self.generate_expression(&e.expression)?;
31118        Ok(())
31119    }
31120
31121    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31122        // [NO] LOG
31123        if e.no.is_some() {
31124            self.write_keyword("NO ");
31125        }
31126        self.write_keyword("LOG");
31127        Ok(())
31128    }
31129
31130    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31131        // MD5(this, expressions...)
31132        self.write_keyword("MD5");
31133        self.write("(");
31134        self.generate_expression(&e.this)?;
31135        for expr in &e.expressions {
31136            self.write(", ");
31137            self.generate_expression(expr)?;
31138        }
31139        self.write(")");
31140        Ok(())
31141    }
31142
31143    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31144        // ML.FORECAST(model, [params])
31145        self.write_keyword("ML.FORECAST");
31146        self.write("(");
31147        self.generate_expression(&e.this)?;
31148        if let Some(expression) = &e.expression {
31149            self.write(", ");
31150            self.generate_expression(expression)?;
31151        }
31152        if let Some(params) = &e.params_struct {
31153            self.write(", ");
31154            self.generate_expression(params)?;
31155        }
31156        self.write(")");
31157        Ok(())
31158    }
31159
31160    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31161        // ML.TRANSLATE(model, input, [params])
31162        self.write_keyword("ML.TRANSLATE");
31163        self.write("(");
31164        self.generate_expression(&e.this)?;
31165        self.write(", ");
31166        self.generate_expression(&e.expression)?;
31167        if let Some(params) = &e.params_struct {
31168            self.write(", ");
31169            self.generate_expression(params)?;
31170        }
31171        self.write(")");
31172        Ok(())
31173    }
31174
31175    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31176        // MAKE_INTERVAL(years => x, months => y, ...)
31177        self.write_keyword("MAKE_INTERVAL");
31178        self.write("(");
31179        let mut first = true;
31180        if let Some(year) = &e.year {
31181            self.write("years => ");
31182            self.generate_expression(year)?;
31183            first = false;
31184        }
31185        if let Some(month) = &e.month {
31186            if !first {
31187                self.write(", ");
31188            }
31189            self.write("months => ");
31190            self.generate_expression(month)?;
31191            first = false;
31192        }
31193        if let Some(week) = &e.week {
31194            if !first {
31195                self.write(", ");
31196            }
31197            self.write("weeks => ");
31198            self.generate_expression(week)?;
31199            first = false;
31200        }
31201        if let Some(day) = &e.day {
31202            if !first {
31203                self.write(", ");
31204            }
31205            self.write("days => ");
31206            self.generate_expression(day)?;
31207            first = false;
31208        }
31209        if let Some(hour) = &e.hour {
31210            if !first {
31211                self.write(", ");
31212            }
31213            self.write("hours => ");
31214            self.generate_expression(hour)?;
31215            first = false;
31216        }
31217        if let Some(minute) = &e.minute {
31218            if !first {
31219                self.write(", ");
31220            }
31221            self.write("mins => ");
31222            self.generate_expression(minute)?;
31223            first = false;
31224        }
31225        if let Some(second) = &e.second {
31226            if !first {
31227                self.write(", ");
31228            }
31229            self.write("secs => ");
31230            self.generate_expression(second)?;
31231        }
31232        self.write(")");
31233        Ok(())
31234    }
31235
31236    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31237        // MANHATTAN_DISTANCE(vector1, vector2)
31238        self.write_keyword("MANHATTAN_DISTANCE");
31239        self.write("(");
31240        self.generate_expression(&e.this)?;
31241        self.write(", ");
31242        self.generate_expression(&e.expression)?;
31243        self.write(")");
31244        Ok(())
31245    }
31246
31247    fn generate_map(&mut self, e: &Map) -> Result<()> {
31248        // MAP(key1, value1, key2, value2, ...)
31249        self.write_keyword("MAP");
31250        self.write("(");
31251        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31252            if i > 0 {
31253                self.write(", ");
31254            }
31255            self.generate_expression(key)?;
31256            self.write(", ");
31257            self.generate_expression(value)?;
31258        }
31259        self.write(")");
31260        Ok(())
31261    }
31262
31263    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31264        // MAP_CAT(map1, map2)
31265        self.write_keyword("MAP_CAT");
31266        self.write("(");
31267        self.generate_expression(&e.this)?;
31268        self.write(", ");
31269        self.generate_expression(&e.expression)?;
31270        self.write(")");
31271        Ok(())
31272    }
31273
31274    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31275        // MAP_DELETE(map, key1, key2, ...)
31276        self.write_keyword("MAP_DELETE");
31277        self.write("(");
31278        self.generate_expression(&e.this)?;
31279        for expr in &e.expressions {
31280            self.write(", ");
31281            self.generate_expression(expr)?;
31282        }
31283        self.write(")");
31284        Ok(())
31285    }
31286
31287    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31288        // MAP_INSERT(map, key, value, [update_flag])
31289        self.write_keyword("MAP_INSERT");
31290        self.write("(");
31291        self.generate_expression(&e.this)?;
31292        if let Some(key) = &e.key {
31293            self.write(", ");
31294            self.generate_expression(key)?;
31295        }
31296        if let Some(value) = &e.value {
31297            self.write(", ");
31298            self.generate_expression(value)?;
31299        }
31300        if let Some(update_flag) = &e.update_flag {
31301            self.write(", ");
31302            self.generate_expression(update_flag)?;
31303        }
31304        self.write(")");
31305        Ok(())
31306    }
31307
31308    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31309        // MAP_PICK(map, key1, key2, ...)
31310        self.write_keyword("MAP_PICK");
31311        self.write("(");
31312        self.generate_expression(&e.this)?;
31313        for expr in &e.expressions {
31314            self.write(", ");
31315            self.generate_expression(expr)?;
31316        }
31317        self.write(")");
31318        Ok(())
31319    }
31320
31321    fn generate_masking_policy_column_constraint(
31322        &mut self,
31323        e: &MaskingPolicyColumnConstraint,
31324    ) -> Result<()> {
31325        // Python: MASKING POLICY name [USING (cols)]
31326        self.write_keyword("MASKING POLICY");
31327        self.write_space();
31328        self.generate_expression(&e.this)?;
31329        if !e.expressions.is_empty() {
31330            self.write_keyword(" USING");
31331            self.write(" (");
31332            for (i, expr) in e.expressions.iter().enumerate() {
31333                if i > 0 {
31334                    self.write(", ");
31335                }
31336                self.generate_expression(expr)?;
31337            }
31338            self.write(")");
31339        }
31340        Ok(())
31341    }
31342
31343    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31344        if matches!(
31345            self.config.dialect,
31346            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31347        ) {
31348            if e.expressions.len() > 1 {
31349                self.write("(");
31350            }
31351            for (i, expr) in e.expressions.iter().enumerate() {
31352                if i > 0 {
31353                    self.write_keyword(" OR ");
31354                }
31355                self.generate_expression(expr)?;
31356                self.write_space();
31357                self.write("@@");
31358                self.write_space();
31359                self.generate_expression(&e.this)?;
31360            }
31361            if e.expressions.len() > 1 {
31362                self.write(")");
31363            }
31364            return Ok(());
31365        }
31366
31367        // MATCH(columns) AGAINST(expr [modifier])
31368        self.write_keyword("MATCH");
31369        self.write("(");
31370        for (i, expr) in e.expressions.iter().enumerate() {
31371            if i > 0 {
31372                self.write(", ");
31373            }
31374            self.generate_expression(expr)?;
31375        }
31376        self.write(")");
31377        self.write_keyword(" AGAINST");
31378        self.write("(");
31379        self.generate_expression(&e.this)?;
31380        if let Some(modifier) = &e.modifier {
31381            self.write_space();
31382            self.generate_expression(modifier)?;
31383        }
31384        self.write(")");
31385        Ok(())
31386    }
31387
31388    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31389        // Python: [window_frame] this
31390        if let Some(window_frame) = &e.window_frame {
31391            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31392            self.write_space();
31393        }
31394        self.generate_expression(&e.this)?;
31395        Ok(())
31396    }
31397
31398    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31399        // MATERIALIZED [this]
31400        self.write_keyword("MATERIALIZED");
31401        if let Some(this) = &e.this {
31402            self.write_space();
31403            self.generate_expression(this)?;
31404        }
31405        Ok(())
31406    }
31407
31408    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31409        // MERGE INTO target USING source ON condition WHEN ...
31410        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31411        if let Some(with_) = &e.with_ {
31412            if let Expression::With(with_clause) = with_.as_ref() {
31413                self.generate_with(with_clause)?;
31414                self.write_space();
31415            } else {
31416                self.generate_expression(with_)?;
31417                self.write_space();
31418            }
31419        }
31420        self.write_keyword("MERGE INTO");
31421        self.write_space();
31422        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31423            if let Expression::Alias(alias) = e.this.as_ref() {
31424                self.generate_expression(&alias.this)?;
31425                self.write_space();
31426                self.generate_identifier(&alias.alias)?;
31427            } else {
31428                self.generate_expression(&e.this)?;
31429            }
31430        } else {
31431            self.generate_expression(&e.this)?;
31432        }
31433
31434        // USING clause - newline before in pretty mode
31435        if self.config.pretty {
31436            self.write_newline();
31437            self.write_indent();
31438        } else {
31439            self.write_space();
31440        }
31441        self.write_keyword("USING");
31442        self.write_space();
31443        self.generate_expression(&e.using)?;
31444
31445        // ON clause - newline before in pretty mode
31446        if let Some(on) = &e.on {
31447            if self.config.pretty {
31448                self.write_newline();
31449                self.write_indent();
31450            } else {
31451                self.write_space();
31452            }
31453            self.write_keyword("ON");
31454            self.write_space();
31455            self.generate_expression(on)?;
31456        }
31457        // DuckDB USING (key_columns) clause
31458        if let Some(using_cond) = &e.using_cond {
31459            self.write_space();
31460            self.write_keyword("USING");
31461            self.write_space();
31462            self.write("(");
31463            // using_cond is a Tuple containing the column identifiers
31464            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31465                for (i, col) in tuple.expressions.iter().enumerate() {
31466                    if i > 0 {
31467                        self.write(", ");
31468                    }
31469                    self.generate_expression(col)?;
31470                }
31471            } else {
31472                self.generate_expression(using_cond)?;
31473            }
31474            self.write(")");
31475        }
31476        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
31477        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
31478        if matches!(
31479            self.config.dialect,
31480            Some(crate::DialectType::PostgreSQL)
31481                | Some(crate::DialectType::Redshift)
31482                | Some(crate::DialectType::Trino)
31483                | Some(crate::DialectType::Presto)
31484                | Some(crate::DialectType::Athena)
31485        ) {
31486            let mut names = Vec::new();
31487            match e.this.as_ref() {
31488                Expression::Alias(a) => {
31489                    // e.g., "x AS z" -> strip both "x" and "z"
31490                    if let Expression::Table(t) = &a.this {
31491                        names.push(t.name.name.clone());
31492                    } else if let Expression::Identifier(id) = &a.this {
31493                        names.push(id.name.clone());
31494                    }
31495                    names.push(a.alias.name.clone());
31496                }
31497                Expression::Table(t) => {
31498                    names.push(t.name.name.clone());
31499                }
31500                Expression::Identifier(id) => {
31501                    names.push(id.name.clone());
31502                }
31503                _ => {}
31504            }
31505            self.merge_strip_qualifiers = names;
31506        }
31507
31508        // WHEN clauses - newline before each in pretty mode
31509        if let Some(whens) = &e.whens {
31510            if self.config.pretty {
31511                self.write_newline();
31512                self.write_indent();
31513            } else {
31514                self.write_space();
31515            }
31516            self.generate_expression(whens)?;
31517        }
31518
31519        // Restore merge_strip_qualifiers
31520        self.merge_strip_qualifiers = saved_merge_strip;
31521
31522        // OUTPUT/RETURNING clause - newline before in pretty mode
31523        if let Some(returning) = &e.returning {
31524            if self.config.pretty {
31525                self.write_newline();
31526                self.write_indent();
31527            } else {
31528                self.write_space();
31529            }
31530            self.generate_expression(returning)?;
31531        }
31532        Ok(())
31533    }
31534
31535    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
31536        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
31537        if e.no.is_some() {
31538            self.write_keyword("NO MERGEBLOCKRATIO");
31539        } else if e.default.is_some() {
31540            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
31541        } else {
31542            self.write_keyword("MERGEBLOCKRATIO");
31543            self.write("=");
31544            if let Some(this) = &e.this {
31545                self.generate_expression(this)?;
31546            }
31547            if e.percent.is_some() {
31548                self.write_keyword(" PERCENT");
31549            }
31550        }
31551        Ok(())
31552    }
31553
31554    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
31555        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
31556        self.write_keyword("TTL");
31557        let pretty_clickhouse = self.config.pretty
31558            && matches!(
31559                self.config.dialect,
31560                Some(crate::dialects::DialectType::ClickHouse)
31561            );
31562
31563        if pretty_clickhouse {
31564            self.write_newline();
31565            self.indent_level += 1;
31566            for (i, expr) in e.expressions.iter().enumerate() {
31567                if i > 0 {
31568                    self.write(",");
31569                    self.write_newline();
31570                }
31571                self.write_indent();
31572                self.generate_expression(expr)?;
31573            }
31574            self.indent_level -= 1;
31575        } else {
31576            self.write_space();
31577            for (i, expr) in e.expressions.iter().enumerate() {
31578                if i > 0 {
31579                    self.write(", ");
31580                }
31581                self.generate_expression(expr)?;
31582            }
31583        }
31584
31585        if let Some(where_) = &e.where_ {
31586            if pretty_clickhouse {
31587                self.write_newline();
31588                if let Expression::Where(w) = where_.as_ref() {
31589                    self.write_indent();
31590                    self.write_keyword("WHERE");
31591                    self.write_newline();
31592                    self.indent_level += 1;
31593                    self.write_indent();
31594                    self.generate_expression(&w.this)?;
31595                    self.indent_level -= 1;
31596                } else {
31597                    self.write_indent();
31598                    self.generate_expression(where_)?;
31599                }
31600            } else {
31601                self.write_space();
31602                self.generate_expression(where_)?;
31603            }
31604        }
31605        if let Some(group) = &e.group {
31606            if pretty_clickhouse {
31607                self.write_newline();
31608                if let Expression::Group(g) = group.as_ref() {
31609                    self.write_indent();
31610                    self.write_keyword("GROUP BY");
31611                    self.write_newline();
31612                    self.indent_level += 1;
31613                    for (i, expr) in g.expressions.iter().enumerate() {
31614                        if i > 0 {
31615                            self.write(",");
31616                            self.write_newline();
31617                        }
31618                        self.write_indent();
31619                        self.generate_expression(expr)?;
31620                    }
31621                    self.indent_level -= 1;
31622                } else {
31623                    self.write_indent();
31624                    self.generate_expression(group)?;
31625                }
31626            } else {
31627                self.write_space();
31628                self.generate_expression(group)?;
31629            }
31630        }
31631        if let Some(aggregates) = &e.aggregates {
31632            if pretty_clickhouse {
31633                self.write_newline();
31634                self.write_indent();
31635                self.write_keyword("SET");
31636                self.write_newline();
31637                self.indent_level += 1;
31638                if let Expression::Tuple(t) = aggregates.as_ref() {
31639                    for (i, agg) in t.expressions.iter().enumerate() {
31640                        if i > 0 {
31641                            self.write(",");
31642                            self.write_newline();
31643                        }
31644                        self.write_indent();
31645                        self.generate_expression(agg)?;
31646                    }
31647                } else {
31648                    self.write_indent();
31649                    self.generate_expression(aggregates)?;
31650                }
31651                self.indent_level -= 1;
31652            } else {
31653                self.write_space();
31654                self.write_keyword("SET");
31655                self.write_space();
31656                if let Expression::Tuple(t) = aggregates.as_ref() {
31657                    for (i, agg) in t.expressions.iter().enumerate() {
31658                        if i > 0 {
31659                            self.write(", ");
31660                        }
31661                        self.generate_expression(agg)?;
31662                    }
31663                } else {
31664                    self.generate_expression(aggregates)?;
31665                }
31666            }
31667        }
31668        Ok(())
31669    }
31670
31671    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31672        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31673        self.generate_expression(&e.this)?;
31674        if e.delete.is_some() {
31675            self.write_keyword(" DELETE");
31676        }
31677        if let Some(recompress) = &e.recompress {
31678            self.write_keyword(" RECOMPRESS ");
31679            self.generate_expression(recompress)?;
31680        }
31681        if let Some(to_disk) = &e.to_disk {
31682            self.write_keyword(" TO DISK ");
31683            self.generate_expression(to_disk)?;
31684        }
31685        if let Some(to_volume) = &e.to_volume {
31686            self.write_keyword(" TO VOLUME ");
31687            self.generate_expression(to_volume)?;
31688        }
31689        Ok(())
31690    }
31691
31692    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31693        // MINHASH(this, expressions...)
31694        self.write_keyword("MINHASH");
31695        self.write("(");
31696        self.generate_expression(&e.this)?;
31697        for expr in &e.expressions {
31698            self.write(", ");
31699            self.generate_expression(expr)?;
31700        }
31701        self.write(")");
31702        Ok(())
31703    }
31704
31705    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31706        // model!attribute - Snowflake syntax
31707        self.generate_expression(&e.this)?;
31708        self.write("!");
31709        self.generate_expression(&e.expression)?;
31710        Ok(())
31711    }
31712
31713    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31714        // MONTHNAME(this)
31715        self.write_keyword("MONTHNAME");
31716        self.write("(");
31717        self.generate_expression(&e.this)?;
31718        self.write(")");
31719        Ok(())
31720    }
31721
31722    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31723        // Output leading comments
31724        for comment in &e.leading_comments {
31725            self.write_formatted_comment(comment);
31726            if self.config.pretty {
31727                self.write_newline();
31728                self.write_indent();
31729            } else {
31730                self.write_space();
31731            }
31732        }
31733        // Python: INSERT [OVERWRITE] kind expressions source
31734        self.write_keyword("INSERT");
31735        if e.overwrite {
31736            self.write_space();
31737            self.write_keyword("OVERWRITE");
31738        }
31739        self.write_space();
31740        self.write(&e.kind);
31741        if self.config.pretty {
31742            self.indent_level += 1;
31743            for expr in &e.expressions {
31744                self.write_newline();
31745                self.write_indent();
31746                self.generate_expression(expr)?;
31747            }
31748            self.indent_level -= 1;
31749        } else {
31750            for expr in &e.expressions {
31751                self.write_space();
31752                self.generate_expression(expr)?;
31753            }
31754        }
31755        if let Some(source) = &e.source {
31756            if self.config.pretty {
31757                self.write_newline();
31758                self.write_indent();
31759            } else {
31760                self.write_space();
31761            }
31762            self.generate_expression(source)?;
31763        }
31764        Ok(())
31765    }
31766
31767    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31768        // Python: NEXT VALUE FOR this [OVER (order)]
31769        self.write_keyword("NEXT VALUE FOR");
31770        self.write_space();
31771        self.generate_expression(&e.this)?;
31772        if let Some(order) = &e.order {
31773            self.write_space();
31774            self.write_keyword("OVER");
31775            self.write(" (");
31776            self.generate_expression(order)?;
31777            self.write(")");
31778        }
31779        Ok(())
31780    }
31781
31782    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31783        // NORMAL(mean, stddev, gen)
31784        self.write_keyword("NORMAL");
31785        self.write("(");
31786        self.generate_expression(&e.this)?;
31787        if let Some(stddev) = &e.stddev {
31788            self.write(", ");
31789            self.generate_expression(stddev)?;
31790        }
31791        if let Some(gen) = &e.gen {
31792            self.write(", ");
31793            self.generate_expression(gen)?;
31794        }
31795        self.write(")");
31796        Ok(())
31797    }
31798
31799    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31800        // NORMALIZE(this, form) or CASEFOLD version
31801        if e.is_casefold.is_some() {
31802            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31803        } else {
31804            self.write_keyword("NORMALIZE");
31805        }
31806        self.write("(");
31807        self.generate_expression(&e.this)?;
31808        if let Some(form) = &e.form {
31809            self.write(", ");
31810            self.generate_expression(form)?;
31811        }
31812        self.write(")");
31813        Ok(())
31814    }
31815
31816    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31817        // Python: [NOT ]NULL
31818        if e.allow_null.is_none() {
31819            self.write_keyword("NOT ");
31820        }
31821        self.write_keyword("NULL");
31822        Ok(())
31823    }
31824
31825    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31826        // NULLIF(this, expression)
31827        self.write_keyword("NULLIF");
31828        self.write("(");
31829        self.generate_expression(&e.this)?;
31830        self.write(", ");
31831        self.generate_expression(&e.expression)?;
31832        self.write(")");
31833        Ok(())
31834    }
31835
31836    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31837        // FORMAT(this, format, culture)
31838        self.write_keyword("FORMAT");
31839        self.write("(");
31840        self.generate_expression(&e.this)?;
31841        self.write(", '");
31842        self.write(&e.format);
31843        self.write("'");
31844        if let Some(culture) = &e.culture {
31845            self.write(", ");
31846            self.generate_expression(culture)?;
31847        }
31848        self.write(")");
31849        Ok(())
31850    }
31851
31852    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31853        // OBJECT_AGG(key, value)
31854        self.write_keyword("OBJECT_AGG");
31855        self.write("(");
31856        self.generate_expression(&e.this)?;
31857        self.write(", ");
31858        self.generate_expression(&e.expression)?;
31859        self.write(")");
31860        Ok(())
31861    }
31862
31863    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31864        // Python: Just returns the name
31865        self.generate_expression(&e.this)?;
31866        Ok(())
31867    }
31868
31869    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31870        // OBJECT_INSERT(obj, key, value, [update_flag])
31871        self.write_keyword("OBJECT_INSERT");
31872        self.write("(");
31873        self.generate_expression(&e.this)?;
31874        if let Some(key) = &e.key {
31875            self.write(", ");
31876            self.generate_expression(key)?;
31877        }
31878        if let Some(value) = &e.value {
31879            self.write(", ");
31880            self.generate_expression(value)?;
31881        }
31882        if let Some(update_flag) = &e.update_flag {
31883            self.write(", ");
31884            self.generate_expression(update_flag)?;
31885        }
31886        self.write(")");
31887        Ok(())
31888    }
31889
31890    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31891        // OFFSET value [ROW|ROWS]
31892        self.write_keyword("OFFSET");
31893        self.write_space();
31894        self.generate_expression(&e.this)?;
31895        // Output ROWS keyword only for TSQL/Oracle targets
31896        if e.rows == Some(true)
31897            && matches!(
31898                self.config.dialect,
31899                Some(crate::dialects::DialectType::TSQL)
31900                    | Some(crate::dialects::DialectType::Oracle)
31901            )
31902        {
31903            self.write_space();
31904            self.write_keyword("ROWS");
31905        }
31906        Ok(())
31907    }
31908
31909    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31910        // QUALIFY condition (Snowflake/BigQuery)
31911        self.write_keyword("QUALIFY");
31912        self.write_space();
31913        self.generate_expression(&e.this)?;
31914        Ok(())
31915    }
31916
31917    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31918        // ON CLUSTER cluster_name
31919        self.write_keyword("ON CLUSTER");
31920        self.write_space();
31921        self.generate_expression(&e.this)?;
31922        Ok(())
31923    }
31924
31925    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31926        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31927        self.write_keyword("ON COMMIT");
31928        if e.delete.is_some() {
31929            self.write_keyword(" DELETE ROWS");
31930        } else {
31931            self.write_keyword(" PRESERVE ROWS");
31932        }
31933        Ok(())
31934    }
31935
31936    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31937        // Python: error/empty/null handling
31938        if let Some(empty) = &e.empty {
31939            self.generate_expression(empty)?;
31940            self.write_keyword(" ON EMPTY");
31941        }
31942        if let Some(error) = &e.error {
31943            if e.empty.is_some() {
31944                self.write_space();
31945            }
31946            self.generate_expression(error)?;
31947            self.write_keyword(" ON ERROR");
31948        }
31949        if let Some(null) = &e.null {
31950            if e.empty.is_some() || e.error.is_some() {
31951                self.write_space();
31952            }
31953            self.generate_expression(null)?;
31954            self.write_keyword(" ON NULL");
31955        }
31956        Ok(())
31957    }
31958
31959    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31960        // Materialize doesn't support ON CONFLICT - skip entirely
31961        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31962            return Ok(());
31963        }
31964        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31965        if e.duplicate.is_some() {
31966            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31967            self.write_keyword("ON DUPLICATE KEY UPDATE");
31968            for (i, expr) in e.expressions.iter().enumerate() {
31969                if i > 0 {
31970                    self.write(",");
31971                }
31972                self.write_space();
31973                self.generate_expression(expr)?;
31974            }
31975            return Ok(());
31976        } else {
31977            self.write_keyword("ON CONFLICT");
31978        }
31979        if let Some(constraint) = &e.constraint {
31980            self.write_keyword(" ON CONSTRAINT ");
31981            self.generate_expression(constraint)?;
31982        }
31983        if let Some(conflict_keys) = &e.conflict_keys {
31984            // conflict_keys can be a Tuple containing expressions
31985            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31986                self.write("(");
31987                for (i, expr) in t.expressions.iter().enumerate() {
31988                    if i > 0 {
31989                        self.write(", ");
31990                    }
31991                    self.generate_expression(expr)?;
31992                }
31993                self.write(")");
31994            } else {
31995                self.write("(");
31996                self.generate_expression(conflict_keys)?;
31997                self.write(")");
31998            }
31999        }
32000        if let Some(index_predicate) = &e.index_predicate {
32001            self.write_keyword(" WHERE ");
32002            self.generate_expression(index_predicate)?;
32003        }
32004        if let Some(action) = &e.action {
32005            // Check if action is "NOTHING" or an UPDATE set
32006            if let Expression::Identifier(id) = action.as_ref() {
32007                if id.name.eq_ignore_ascii_case("NOTHING") {
32008                    self.write_keyword(" DO NOTHING");
32009                } else {
32010                    self.write_keyword(" DO ");
32011                    self.generate_expression(action)?;
32012                }
32013            } else if let Expression::Tuple(t) = action.as_ref() {
32014                // DO UPDATE SET col1 = val1, col2 = val2
32015                self.write_keyword(" DO UPDATE SET ");
32016                for (i, expr) in t.expressions.iter().enumerate() {
32017                    if i > 0 {
32018                        self.write(", ");
32019                    }
32020                    self.generate_expression(expr)?;
32021                }
32022            } else {
32023                self.write_keyword(" DO ");
32024                self.generate_expression(action)?;
32025            }
32026        }
32027        // WHERE clause for the UPDATE action
32028        if let Some(where_) = &e.where_ {
32029            self.write_keyword(" WHERE ");
32030            self.generate_expression(where_)?;
32031        }
32032        Ok(())
32033    }
32034
32035    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32036        // ON property_value
32037        self.write_keyword("ON");
32038        self.write_space();
32039        self.generate_expression(&e.this)?;
32040        Ok(())
32041    }
32042
32043    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32044        // Python: this expression (e.g., column opclass)
32045        self.generate_expression(&e.this)?;
32046        self.write_space();
32047        self.generate_expression(&e.expression)?;
32048        Ok(())
32049    }
32050
32051    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32052        // Python: OPENJSON(this[, path]) [WITH (columns)]
32053        self.write_keyword("OPENJSON");
32054        self.write("(");
32055        self.generate_expression(&e.this)?;
32056        if let Some(path) = &e.path {
32057            self.write(", ");
32058            self.generate_expression(path)?;
32059        }
32060        self.write(")");
32061        if !e.expressions.is_empty() {
32062            self.write_keyword(" WITH");
32063            if self.config.pretty {
32064                self.write(" (\n");
32065                self.indent_level += 2;
32066                for (i, expr) in e.expressions.iter().enumerate() {
32067                    if i > 0 {
32068                        self.write(",\n");
32069                    }
32070                    self.write_indent();
32071                    self.generate_expression(expr)?;
32072                }
32073                self.write("\n");
32074                self.indent_level -= 2;
32075                self.write(")");
32076            } else {
32077                self.write(" (");
32078                for (i, expr) in e.expressions.iter().enumerate() {
32079                    if i > 0 {
32080                        self.write(", ");
32081                    }
32082                    self.generate_expression(expr)?;
32083                }
32084                self.write(")");
32085            }
32086        }
32087        Ok(())
32088    }
32089
32090    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32091        // Python: this kind [path] [AS JSON]
32092        self.generate_expression(&e.this)?;
32093        self.write_space();
32094        // Use parsed data_type if available, otherwise fall back to kind string
32095        if let Some(ref dt) = e.data_type {
32096            self.generate_data_type(dt)?;
32097        } else if !e.kind.is_empty() {
32098            self.write(&e.kind);
32099        }
32100        if let Some(path) = &e.path {
32101            self.write_space();
32102            self.generate_expression(path)?;
32103        }
32104        if e.as_json.is_some() {
32105            self.write_keyword(" AS JSON");
32106        }
32107        Ok(())
32108    }
32109
32110    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32111        // this OPERATOR(op) expression
32112        self.generate_expression(&e.this)?;
32113        self.write_space();
32114        if let Some(op) = &e.operator {
32115            self.write_keyword("OPERATOR");
32116            self.write("(");
32117            self.generate_expression(op)?;
32118            self.write(")");
32119        }
32120        // Emit inline comments between OPERATOR() and the RHS
32121        for comment in &e.comments {
32122            self.write_space();
32123            self.write_formatted_comment(comment);
32124        }
32125        self.write_space();
32126        self.generate_expression(&e.expression)?;
32127        Ok(())
32128    }
32129
32130    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32131        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32132        self.write_keyword("ORDER BY");
32133        let pretty_clickhouse_single_paren = self.config.pretty
32134            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32135            && e.expressions.len() == 1
32136            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32137        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32138            && e.expressions.len() == 1
32139            && matches!(e.expressions[0].this, Expression::Tuple(_))
32140            && !e.expressions[0].desc
32141            && e.expressions[0].nulls_first.is_none();
32142
32143        if pretty_clickhouse_single_paren {
32144            self.write_space();
32145            if let Expression::Paren(p) = &e.expressions[0].this {
32146                self.write("(");
32147                self.write_newline();
32148                self.indent_level += 1;
32149                self.write_indent();
32150                self.generate_expression(&p.this)?;
32151                self.indent_level -= 1;
32152                self.write_newline();
32153                self.write(")");
32154            }
32155            return Ok(());
32156        }
32157
32158        if clickhouse_single_tuple {
32159            self.write_space();
32160            if let Expression::Tuple(t) = &e.expressions[0].this {
32161                self.write("(");
32162                for (i, expr) in t.expressions.iter().enumerate() {
32163                    if i > 0 {
32164                        self.write(", ");
32165                    }
32166                    self.generate_expression(expr)?;
32167                }
32168                self.write(")");
32169            }
32170            return Ok(());
32171        }
32172
32173        self.write_space();
32174        for (i, ordered) in e.expressions.iter().enumerate() {
32175            if i > 0 {
32176                self.write(", ");
32177            }
32178            self.generate_expression(&ordered.this)?;
32179            if ordered.desc {
32180                self.write_space();
32181                self.write_keyword("DESC");
32182            } else if ordered.explicit_asc {
32183                self.write_space();
32184                self.write_keyword("ASC");
32185            }
32186            if let Some(nulls_first) = ordered.nulls_first {
32187                // In Dremio, NULLS LAST is the default, so skip generating it
32188                let skip_nulls_last =
32189                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32190                if !skip_nulls_last {
32191                    self.write_space();
32192                    self.write_keyword("NULLS");
32193                    self.write_space();
32194                    if nulls_first {
32195                        self.write_keyword("FIRST");
32196                    } else {
32197                        self.write_keyword("LAST");
32198                    }
32199                }
32200            }
32201        }
32202        Ok(())
32203    }
32204
32205    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32206        // OUTPUT(model)
32207        self.write_keyword("OUTPUT");
32208        self.write("(");
32209        if self.config.pretty {
32210            self.indent_level += 1;
32211            self.write_newline();
32212            self.write_indent();
32213            self.generate_expression(&e.this)?;
32214            self.indent_level -= 1;
32215            self.write_newline();
32216        } else {
32217            self.generate_expression(&e.this)?;
32218        }
32219        self.write(")");
32220        Ok(())
32221    }
32222
32223    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32224        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32225        self.write_keyword("TRUNCATE");
32226        if let Some(this) = &e.this {
32227            self.write_space();
32228            self.generate_expression(this)?;
32229        }
32230        if e.with_count.is_some() {
32231            self.write_keyword(" WITH COUNT");
32232        } else {
32233            self.write_keyword(" WITHOUT COUNT");
32234        }
32235        Ok(())
32236    }
32237
32238    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32239        // Python: name(expressions)(params)
32240        self.generate_expression(&e.this)?;
32241        self.write("(");
32242        for (i, expr) in e.expressions.iter().enumerate() {
32243            if i > 0 {
32244                self.write(", ");
32245            }
32246            self.generate_expression(expr)?;
32247        }
32248        self.write(")(");
32249        for (i, param) in e.params.iter().enumerate() {
32250            if i > 0 {
32251                self.write(", ");
32252            }
32253            self.generate_expression(param)?;
32254        }
32255        self.write(")");
32256        Ok(())
32257    }
32258
32259    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32260        // PARSE_DATETIME(format, this) or similar
32261        self.write_keyword("PARSE_DATETIME");
32262        self.write("(");
32263        if let Some(format) = &e.format {
32264            self.write("'");
32265            self.write(format);
32266            self.write("', ");
32267        }
32268        self.generate_expression(&e.this)?;
32269        if let Some(zone) = &e.zone {
32270            self.write(", ");
32271            self.generate_expression(zone)?;
32272        }
32273        self.write(")");
32274        Ok(())
32275    }
32276
32277    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32278        // PARSE_IP(this, type, permissive)
32279        self.write_keyword("PARSE_IP");
32280        self.write("(");
32281        self.generate_expression(&e.this)?;
32282        if let Some(type_) = &e.type_ {
32283            self.write(", ");
32284            self.generate_expression(type_)?;
32285        }
32286        if let Some(permissive) = &e.permissive {
32287            self.write(", ");
32288            self.generate_expression(permissive)?;
32289        }
32290        self.write(")");
32291        Ok(())
32292    }
32293
32294    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32295        // PARSE_JSON(this, [expression])
32296        self.write_keyword("PARSE_JSON");
32297        self.write("(");
32298        self.generate_expression(&e.this)?;
32299        if let Some(expression) = &e.expression {
32300            self.write(", ");
32301            self.generate_expression(expression)?;
32302        }
32303        self.write(")");
32304        Ok(())
32305    }
32306
32307    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32308        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32309        self.write_keyword("PARSE_TIME");
32310        self.write("(");
32311        self.write(&format!("'{}'", e.format));
32312        self.write(", ");
32313        self.generate_expression(&e.this)?;
32314        self.write(")");
32315        Ok(())
32316    }
32317
32318    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32319        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32320        self.write_keyword("PARSE_URL");
32321        self.write("(");
32322        self.generate_expression(&e.this)?;
32323        if let Some(part) = &e.part_to_extract {
32324            self.write(", ");
32325            self.generate_expression(part)?;
32326        }
32327        if let Some(key) = &e.key {
32328            self.write(", ");
32329            self.generate_expression(key)?;
32330        }
32331        if let Some(permissive) = &e.permissive {
32332            self.write(", ");
32333            self.generate_expression(permissive)?;
32334        }
32335        self.write(")");
32336        Ok(())
32337    }
32338
32339    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32340        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32341        if e.subpartition {
32342            self.write_keyword("SUBPARTITION");
32343        } else {
32344            self.write_keyword("PARTITION");
32345        }
32346        self.write("(");
32347        for (i, expr) in e.expressions.iter().enumerate() {
32348            if i > 0 {
32349                self.write(", ");
32350            }
32351            self.generate_expression(expr)?;
32352        }
32353        self.write(")");
32354        Ok(())
32355    }
32356
32357    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32358        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32359        if let Some(this) = &e.this {
32360            if let Some(expression) = &e.expression {
32361                // WITH (MODULUS this, REMAINDER expression)
32362                self.write_keyword("WITH");
32363                self.write(" (");
32364                self.write_keyword("MODULUS");
32365                self.write_space();
32366                self.generate_expression(this)?;
32367                self.write(", ");
32368                self.write_keyword("REMAINDER");
32369                self.write_space();
32370                self.generate_expression(expression)?;
32371                self.write(")");
32372            } else {
32373                // IN (this) - this could be a list
32374                self.write_keyword("IN");
32375                self.write(" (");
32376                self.generate_partition_bound_values(this)?;
32377                self.write(")");
32378            }
32379        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32380            // FROM (from_expressions) TO (to_expressions)
32381            self.write_keyword("FROM");
32382            self.write(" (");
32383            self.generate_partition_bound_values(from)?;
32384            self.write(") ");
32385            self.write_keyword("TO");
32386            self.write(" (");
32387            self.generate_partition_bound_values(to)?;
32388            self.write(")");
32389        }
32390        Ok(())
32391    }
32392
32393    /// Generate partition bound values - handles Tuple expressions by outputting
32394    /// contents without wrapping parens (since caller provides the parens)
32395    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32396        if let Expression::Tuple(t) = expr {
32397            for (i, e) in t.expressions.iter().enumerate() {
32398                if i > 0 {
32399                    self.write(", ");
32400                }
32401                self.generate_expression(e)?;
32402            }
32403            Ok(())
32404        } else {
32405            self.generate_expression(expr)
32406        }
32407    }
32408
32409    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32410        // PARTITION BY LIST (partition_expressions) (create_expressions)
32411        self.write_keyword("PARTITION BY LIST");
32412        if let Some(partition_exprs) = &e.partition_expressions {
32413            self.write(" (");
32414            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32415            self.generate_doris_partition_expressions(partition_exprs)?;
32416            self.write(")");
32417        }
32418        if let Some(create_exprs) = &e.create_expressions {
32419            self.write(" (");
32420            // Unwrap Tuple for partition definitions
32421            self.generate_doris_partition_definitions(create_exprs)?;
32422            self.write(")");
32423        }
32424        Ok(())
32425    }
32426
32427    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32428        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32429        self.write_keyword("PARTITION BY RANGE");
32430        if let Some(partition_exprs) = &e.partition_expressions {
32431            self.write(" (");
32432            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32433            self.generate_doris_partition_expressions(partition_exprs)?;
32434            self.write(")");
32435        }
32436        if let Some(create_exprs) = &e.create_expressions {
32437            self.write(" (");
32438            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32439            self.generate_doris_partition_definitions(create_exprs)?;
32440            self.write(")");
32441        }
32442        Ok(())
32443    }
32444
32445    /// Generate Doris partition column expressions (unwrap Tuple)
32446    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32447        if let Expression::Tuple(t) = expr {
32448            for (i, e) in t.expressions.iter().enumerate() {
32449                if i > 0 {
32450                    self.write(", ");
32451                }
32452                self.generate_expression(e)?;
32453            }
32454        } else {
32455            self.generate_expression(expr)?;
32456        }
32457        Ok(())
32458    }
32459
32460    /// Generate Doris partition definitions (comma-separated Partition expressions)
32461    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32462        match expr {
32463            Expression::Tuple(t) => {
32464                // Multiple partitions, comma-separated
32465                for (i, part) in t.expressions.iter().enumerate() {
32466                    if i > 0 {
32467                        self.write(", ");
32468                    }
32469                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
32470                    if let Expression::Partition(p) = part {
32471                        for (j, inner) in p.expressions.iter().enumerate() {
32472                            if j > 0 {
32473                                self.write(", ");
32474                            }
32475                            self.generate_expression(inner)?;
32476                        }
32477                    } else {
32478                        self.generate_expression(part)?;
32479                    }
32480                }
32481            }
32482            Expression::PartitionByRangePropertyDynamic(_) => {
32483                // Dynamic partition - FROM/TO/INTERVAL
32484                self.generate_expression(expr)?;
32485            }
32486            _ => {
32487                self.generate_expression(expr)?;
32488            }
32489        }
32490        Ok(())
32491    }
32492
32493    fn generate_partition_by_range_property_dynamic(
32494        &mut self,
32495        e: &PartitionByRangePropertyDynamic,
32496    ) -> Result<()> {
32497        if e.use_start_end {
32498            // StarRocks: START ('val') END ('val') EVERY (expr)
32499            if let Some(start) = &e.start {
32500                self.write_keyword("START");
32501                self.write(" (");
32502                self.generate_expression(start)?;
32503                self.write(")");
32504            }
32505            if let Some(end) = &e.end {
32506                self.write_space();
32507                self.write_keyword("END");
32508                self.write(" (");
32509                self.generate_expression(end)?;
32510                self.write(")");
32511            }
32512            if let Some(every) = &e.every {
32513                self.write_space();
32514                self.write_keyword("EVERY");
32515                self.write(" (");
32516                // Use unquoted interval format for StarRocks
32517                self.generate_doris_interval(every)?;
32518                self.write(")");
32519            }
32520        } else {
32521            // Doris: FROM (start) TO (end) INTERVAL n UNIT
32522            if let Some(start) = &e.start {
32523                self.write_keyword("FROM");
32524                self.write(" (");
32525                self.generate_expression(start)?;
32526                self.write(")");
32527            }
32528            if let Some(end) = &e.end {
32529                self.write_space();
32530                self.write_keyword("TO");
32531                self.write(" (");
32532                self.generate_expression(end)?;
32533                self.write(")");
32534            }
32535            if let Some(every) = &e.every {
32536                self.write_space();
32537                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
32538                self.generate_doris_interval(every)?;
32539            }
32540        }
32541        Ok(())
32542    }
32543
32544    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
32545    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
32546        if let Expression::Interval(interval) = expr {
32547            self.write_keyword("INTERVAL");
32548            if let Some(ref value) = interval.this {
32549                self.write_space();
32550                // If the value is a string literal that looks like a number,
32551                // output it without quotes (matching Python sqlglot's
32552                // partitionbyrangepropertydynamic_sql which converts back to number)
32553                match value {
32554                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-') && !s.is_empty()) => {
32555                        if let Literal::String(s) = lit.as_ref() {
32556                            self.write(s);
32557                        }
32558                    }
32559                    _ => {
32560                        self.generate_expression(value)?;
32561                    }
32562                }
32563            }
32564            if let Some(ref unit_spec) = interval.unit {
32565                self.write_space();
32566                self.write_interval_unit_spec(unit_spec)?;
32567            }
32568            Ok(())
32569        } else {
32570            self.generate_expression(expr)
32571        }
32572    }
32573
32574    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
32575        // TRUNCATE(expression, this)
32576        self.write_keyword("TRUNCATE");
32577        self.write("(");
32578        self.generate_expression(&e.expression)?;
32579        self.write(", ");
32580        self.generate_expression(&e.this)?;
32581        self.write(")");
32582        Ok(())
32583    }
32584
32585    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
32586        // Doris: PARTITION name VALUES IN (val1, val2)
32587        self.write_keyword("PARTITION");
32588        self.write_space();
32589        self.generate_expression(&e.this)?;
32590        self.write_space();
32591        self.write_keyword("VALUES IN");
32592        self.write(" (");
32593        for (i, expr) in e.expressions.iter().enumerate() {
32594            if i > 0 {
32595                self.write(", ");
32596            }
32597            self.generate_expression(expr)?;
32598        }
32599        self.write(")");
32600        Ok(())
32601    }
32602
32603    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32604        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32605        // TSQL ranges have no expressions and just use `this TO expression`
32606        if e.expressions.is_empty() && e.expression.is_some() {
32607            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32608            self.generate_expression(&e.this)?;
32609            self.write_space();
32610            self.write_keyword("TO");
32611            self.write_space();
32612            self.generate_expression(e.expression.as_ref().unwrap())?;
32613            return Ok(());
32614        }
32615
32616        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32617        self.write_keyword("PARTITION");
32618        self.write_space();
32619        self.generate_expression(&e.this)?;
32620        self.write_space();
32621
32622        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32623        if e.expressions.len() == 1 {
32624            // Single value: VALUES LESS THAN (val)
32625            self.write_keyword("VALUES LESS THAN");
32626            self.write(" (");
32627            self.generate_expression(&e.expressions[0])?;
32628            self.write(")");
32629        } else if !e.expressions.is_empty() {
32630            // Multiple values with Tuple: VALUES [(val1), (val2))
32631            self.write_keyword("VALUES");
32632            self.write(" [");
32633            for (i, expr) in e.expressions.iter().enumerate() {
32634                if i > 0 {
32635                    self.write(", ");
32636                }
32637                // If the expr is a Tuple, generate its contents wrapped in parens
32638                if let Expression::Tuple(t) = expr {
32639                    self.write("(");
32640                    for (j, inner) in t.expressions.iter().enumerate() {
32641                        if j > 0 {
32642                            self.write(", ");
32643                        }
32644                        self.generate_expression(inner)?;
32645                    }
32646                    self.write(")");
32647                } else {
32648                    self.write("(");
32649                    self.generate_expression(expr)?;
32650                    self.write(")");
32651                }
32652            }
32653            self.write(")");
32654        }
32655        Ok(())
32656    }
32657
32658    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32659        // BUCKET(this, expression)
32660        self.write_keyword("BUCKET");
32661        self.write("(");
32662        self.generate_expression(&e.this)?;
32663        self.write(", ");
32664        self.generate_expression(&e.expression)?;
32665        self.write(")");
32666        Ok(())
32667    }
32668
32669    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32670        // BigQuery table property: PARTITION BY expression [, expression ...]
32671        self.write_keyword("PARTITION BY");
32672        self.write_space();
32673        for (i, expr) in e.expressions.iter().enumerate() {
32674            if i > 0 {
32675                self.write(", ");
32676            }
32677            self.generate_expression(expr)?;
32678        }
32679        Ok(())
32680    }
32681
32682    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32683        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32684        if matches!(
32685            self.config.dialect,
32686            Some(crate::dialects::DialectType::Teradata)
32687                | Some(crate::dialects::DialectType::ClickHouse)
32688        ) {
32689            self.write_keyword("PARTITION BY");
32690        } else {
32691            self.write_keyword("PARTITIONED BY");
32692        }
32693        self.write_space();
32694        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32695        if self.config.pretty {
32696            if let Expression::Tuple(ref tuple) = *e.this {
32697                self.write("(");
32698                self.write_newline();
32699                self.indent_level += 1;
32700                for (i, expr) in tuple.expressions.iter().enumerate() {
32701                    if i > 0 {
32702                        self.write(",");
32703                        self.write_newline();
32704                    }
32705                    self.write_indent();
32706                    self.generate_expression(expr)?;
32707                }
32708                self.indent_level -= 1;
32709                self.write_newline();
32710                self.write(")");
32711            } else {
32712                self.generate_expression(&e.this)?;
32713            }
32714        } else {
32715            self.generate_expression(&e.this)?;
32716        }
32717        Ok(())
32718    }
32719
32720    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32721        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32722        self.write_keyword("PARTITION OF");
32723        self.write_space();
32724        self.generate_expression(&e.this)?;
32725        // Check if expression is a PartitionBoundSpec
32726        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32727            self.write_space();
32728            self.write_keyword("FOR VALUES");
32729            self.write_space();
32730            self.generate_expression(&e.expression)?;
32731        } else {
32732            self.write_space();
32733            self.write_keyword("DEFAULT");
32734        }
32735        Ok(())
32736    }
32737
32738    fn generate_period_for_system_time_constraint(
32739        &mut self,
32740        e: &PeriodForSystemTimeConstraint,
32741    ) -> Result<()> {
32742        // PERIOD FOR SYSTEM_TIME (this, expression)
32743        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32744        self.write(" (");
32745        self.generate_expression(&e.this)?;
32746        self.write(", ");
32747        self.generate_expression(&e.expression)?;
32748        self.write(")");
32749        Ok(())
32750    }
32751
32752    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32753        // value AS alias
32754        // The alias can be an identifier or an expression (e.g., string concatenation)
32755        self.generate_expression(&e.this)?;
32756        self.write_space();
32757        self.write_keyword("AS");
32758        self.write_space();
32759        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32760        if self.config.unpivot_aliases_are_identifiers {
32761            match &e.alias {
32762                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32763                    let Literal::String(s) = lit.as_ref() else {
32764                        unreachable!()
32765                    };
32766                    // Convert string literal to identifier
32767                    self.generate_identifier(&Identifier::new(s.clone()))?;
32768                }
32769                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32770                    let Literal::Number(n) = lit.as_ref() else {
32771                        unreachable!()
32772                    };
32773                    // Convert number literal to quoted identifier
32774                    let mut id = Identifier::new(n.clone());
32775                    id.quoted = true;
32776                    self.generate_identifier(&id)?;
32777                }
32778                other => {
32779                    self.generate_expression(other)?;
32780                }
32781            }
32782        } else {
32783            self.generate_expression(&e.alias)?;
32784        }
32785        Ok(())
32786    }
32787
32788    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32789        // ANY or ANY [expression]
32790        self.write_keyword("ANY");
32791        if let Some(this) = &e.this {
32792            self.write_space();
32793            self.generate_expression(this)?;
32794        }
32795        Ok(())
32796    }
32797
32798    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32799        // ML.PREDICT(MODEL this, expression, [params_struct])
32800        self.write_keyword("ML.PREDICT");
32801        self.write("(");
32802        self.write_keyword("MODEL");
32803        self.write_space();
32804        self.generate_expression(&e.this)?;
32805        self.write(", ");
32806        self.generate_expression(&e.expression)?;
32807        if let Some(params) = &e.params_struct {
32808            self.write(", ");
32809            self.generate_expression(params)?;
32810        }
32811        self.write(")");
32812        Ok(())
32813    }
32814
32815    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32816        // PREVIOUS_DAY(this, expression)
32817        self.write_keyword("PREVIOUS_DAY");
32818        self.write("(");
32819        self.generate_expression(&e.this)?;
32820        self.write(", ");
32821        self.generate_expression(&e.expression)?;
32822        self.write(")");
32823        Ok(())
32824    }
32825
32826    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32827        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32828        self.write_keyword("PRIMARY KEY");
32829        if let Some(name) = &e.this {
32830            self.write_space();
32831            self.generate_expression(name)?;
32832        }
32833        if !e.expressions.is_empty() {
32834            self.write(" (");
32835            for (i, expr) in e.expressions.iter().enumerate() {
32836                if i > 0 {
32837                    self.write(", ");
32838                }
32839                self.generate_expression(expr)?;
32840            }
32841            self.write(")");
32842        }
32843        if let Some(include) = &e.include {
32844            self.write_space();
32845            self.generate_expression(include)?;
32846        }
32847        if !e.options.is_empty() {
32848            self.write_space();
32849            for (i, opt) in e.options.iter().enumerate() {
32850                if i > 0 {
32851                    self.write_space();
32852                }
32853                self.generate_expression(opt)?;
32854            }
32855        }
32856        Ok(())
32857    }
32858
32859    fn generate_primary_key_column_constraint(
32860        &mut self,
32861        _e: &PrimaryKeyColumnConstraint,
32862    ) -> Result<()> {
32863        // PRIMARY KEY constraint at column level
32864        self.write_keyword("PRIMARY KEY");
32865        Ok(())
32866    }
32867
32868    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32869        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32870        self.write_keyword("PATH");
32871        self.write_space();
32872        self.generate_expression(&e.this)?;
32873        Ok(())
32874    }
32875
32876    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32877        // PROJECTION this (expression)
32878        self.write_keyword("PROJECTION");
32879        self.write_space();
32880        self.generate_expression(&e.this)?;
32881        self.write(" (");
32882        self.generate_expression(&e.expression)?;
32883        self.write(")");
32884        Ok(())
32885    }
32886
32887    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32888        // Properties list
32889        for (i, prop) in e.expressions.iter().enumerate() {
32890            if i > 0 {
32891                self.write(", ");
32892            }
32893            self.generate_expression(prop)?;
32894        }
32895        Ok(())
32896    }
32897
32898    fn generate_property(&mut self, e: &Property) -> Result<()> {
32899        // name=value
32900        self.generate_expression(&e.this)?;
32901        if let Some(value) = &e.value {
32902            self.write("=");
32903            self.generate_expression(value)?;
32904        }
32905        Ok(())
32906    }
32907
32908    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32909        self.write_keyword("OPTIONS");
32910        if e.entries.is_empty() {
32911            self.write(" ()");
32912            return Ok(());
32913        }
32914
32915        if self.config.pretty {
32916            self.write(" (");
32917            self.write_newline();
32918            self.indent_level += 1;
32919            for (i, entry) in e.entries.iter().enumerate() {
32920                if i > 0 {
32921                    self.write(",");
32922                    self.write_newline();
32923                }
32924                self.write_indent();
32925                self.generate_identifier(&entry.key)?;
32926                self.write("=");
32927                self.generate_expression(&entry.value)?;
32928            }
32929            self.indent_level -= 1;
32930            self.write_newline();
32931            self.write(")");
32932        } else {
32933            self.write(" (");
32934            for (i, entry) in e.entries.iter().enumerate() {
32935                if i > 0 {
32936                    self.write(", ");
32937                }
32938                self.generate_identifier(&entry.key)?;
32939                self.write("=");
32940                self.generate_expression(&entry.value)?;
32941            }
32942            self.write(")");
32943        }
32944        Ok(())
32945    }
32946
32947    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32948    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32949        self.write_keyword("OPTIONS");
32950        self.write(" (");
32951        for (i, opt) in options.iter().enumerate() {
32952            if i > 0 {
32953                self.write(", ");
32954            }
32955            self.generate_option_expression(opt)?;
32956        }
32957        self.write(")");
32958        Ok(())
32959    }
32960
32961    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32962    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32963        self.write_keyword("PROPERTIES");
32964        self.write(" (");
32965        for (i, prop) in properties.iter().enumerate() {
32966            if i > 0 {
32967                self.write(", ");
32968            }
32969            self.generate_option_expression(prop)?;
32970        }
32971        self.write(")");
32972        Ok(())
32973    }
32974
32975    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32976    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32977        self.write_keyword("ENVIRONMENT");
32978        self.write(" (");
32979        for (i, env_item) in environment.iter().enumerate() {
32980            if i > 0 {
32981                self.write(", ");
32982            }
32983            self.generate_environment_expression(env_item)?;
32984        }
32985        self.write(")");
32986        Ok(())
32987    }
32988
32989    /// Generate an environment expression with spaces around =
32990    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32991        match expr {
32992            Expression::Eq(eq) => {
32993                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32994                self.generate_expression(&eq.left)?;
32995                self.write(" = ");
32996                self.generate_expression(&eq.right)?;
32997                Ok(())
32998            }
32999            _ => self.generate_expression(expr),
33000        }
33001    }
33002
33003    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33004    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33005        self.write_keyword("TBLPROPERTIES");
33006        if self.config.pretty {
33007            self.write(" (");
33008            self.write_newline();
33009            self.indent_level += 1;
33010            for (i, opt) in options.iter().enumerate() {
33011                if i > 0 {
33012                    self.write(",");
33013                    self.write_newline();
33014                }
33015                self.write_indent();
33016                self.generate_option_expression(opt)?;
33017            }
33018            self.indent_level -= 1;
33019            self.write_newline();
33020            self.write(")");
33021        } else {
33022            self.write(" (");
33023            for (i, opt) in options.iter().enumerate() {
33024                if i > 0 {
33025                    self.write(", ");
33026                }
33027                self.generate_option_expression(opt)?;
33028            }
33029            self.write(")");
33030        }
33031        Ok(())
33032    }
33033
33034    /// Generate an option expression without spaces around =
33035    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33036        match expr {
33037            Expression::Eq(eq) => {
33038                // Generate key=value without spaces
33039                self.generate_expression(&eq.left)?;
33040                self.write("=");
33041                self.generate_expression(&eq.right)?;
33042                Ok(())
33043            }
33044            _ => self.generate_expression(expr),
33045        }
33046    }
33047
33048    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33049        // Just output the name
33050        self.generate_expression(&e.this)?;
33051        Ok(())
33052    }
33053
33054    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33055        // PUT source_file @stage [options]
33056        self.write_keyword("PUT");
33057        self.write_space();
33058
33059        // Source file path - preserve original quoting
33060        if e.source_quoted {
33061            self.write("'");
33062            self.write(&e.source);
33063            self.write("'");
33064        } else {
33065            self.write(&e.source);
33066        }
33067
33068        self.write_space();
33069
33070        // Target stage reference - output the string directly (includes @)
33071        if let Expression::Literal(lit) = &e.target {
33072            if let Literal::String(s) = lit.as_ref() {
33073                self.write(s);
33074            }
33075        } else {
33076            self.generate_expression(&e.target)?;
33077        }
33078
33079        // Optional parameters: KEY=VALUE
33080        for param in &e.params {
33081            self.write_space();
33082            self.write(&param.name);
33083            if let Some(ref value) = param.value {
33084                self.write("=");
33085                self.generate_expression(value)?;
33086            }
33087        }
33088
33089        Ok(())
33090    }
33091
33092    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33093        // QUANTILE(this, quantile)
33094        self.write_keyword("QUANTILE");
33095        self.write("(");
33096        self.generate_expression(&e.this)?;
33097        if let Some(quantile) = &e.quantile {
33098            self.write(", ");
33099            self.generate_expression(quantile)?;
33100        }
33101        self.write(")");
33102        Ok(())
33103    }
33104
33105    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33106        // QUERY_BAND = this [UPDATE] [FOR scope]
33107        if matches!(
33108            self.config.dialect,
33109            Some(crate::dialects::DialectType::Teradata)
33110        ) {
33111            self.write_keyword("SET");
33112            self.write_space();
33113        }
33114        self.write_keyword("QUERY_BAND");
33115        self.write(" = ");
33116        self.generate_expression(&e.this)?;
33117        if e.update.is_some() {
33118            self.write_space();
33119            self.write_keyword("UPDATE");
33120        }
33121        if let Some(scope) = &e.scope {
33122            self.write_space();
33123            self.write_keyword("FOR");
33124            self.write_space();
33125            self.generate_expression(scope)?;
33126        }
33127        Ok(())
33128    }
33129
33130    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33131        // this = expression
33132        self.generate_expression(&e.this)?;
33133        if let Some(expression) = &e.expression {
33134            self.write(" = ");
33135            self.generate_expression(expression)?;
33136        }
33137        Ok(())
33138    }
33139
33140    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33141        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33142        self.write_keyword("TRANSFORM");
33143        self.write("(");
33144        for (i, expr) in e.expressions.iter().enumerate() {
33145            if i > 0 {
33146                self.write(", ");
33147            }
33148            self.generate_expression(expr)?;
33149        }
33150        self.write(")");
33151        if let Some(row_format_before) = &e.row_format_before {
33152            self.write_space();
33153            self.generate_expression(row_format_before)?;
33154        }
33155        if let Some(record_writer) = &e.record_writer {
33156            self.write_space();
33157            self.write_keyword("RECORDWRITER");
33158            self.write_space();
33159            self.generate_expression(record_writer)?;
33160        }
33161        if let Some(command_script) = &e.command_script {
33162            self.write_space();
33163            self.write_keyword("USING");
33164            self.write_space();
33165            self.generate_expression(command_script)?;
33166        }
33167        if let Some(schema) = &e.schema {
33168            self.write_space();
33169            self.write_keyword("AS");
33170            self.write_space();
33171            self.generate_expression(schema)?;
33172        }
33173        if let Some(row_format_after) = &e.row_format_after {
33174            self.write_space();
33175            self.generate_expression(row_format_after)?;
33176        }
33177        if let Some(record_reader) = &e.record_reader {
33178            self.write_space();
33179            self.write_keyword("RECORDREADER");
33180            self.write_space();
33181            self.generate_expression(record_reader)?;
33182        }
33183        Ok(())
33184    }
33185
33186    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33187        // RANDN([seed])
33188        self.write_keyword("RANDN");
33189        self.write("(");
33190        if let Some(this) = &e.this {
33191            self.generate_expression(this)?;
33192        }
33193        self.write(")");
33194        Ok(())
33195    }
33196
33197    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33198        // RANDSTR(this, [generator])
33199        self.write_keyword("RANDSTR");
33200        self.write("(");
33201        self.generate_expression(&e.this)?;
33202        if let Some(generator) = &e.generator {
33203            self.write(", ");
33204            self.generate_expression(generator)?;
33205        }
33206        self.write(")");
33207        Ok(())
33208    }
33209
33210    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33211        // RANGE_BUCKET(this, expression)
33212        self.write_keyword("RANGE_BUCKET");
33213        self.write("(");
33214        self.generate_expression(&e.this)?;
33215        self.write(", ");
33216        self.generate_expression(&e.expression)?;
33217        self.write(")");
33218        Ok(())
33219    }
33220
33221    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33222        // RANGE_N(this BETWEEN expressions [EACH each])
33223        self.write_keyword("RANGE_N");
33224        self.write("(");
33225        self.generate_expression(&e.this)?;
33226        self.write_space();
33227        self.write_keyword("BETWEEN");
33228        self.write_space();
33229        for (i, expr) in e.expressions.iter().enumerate() {
33230            if i > 0 {
33231                self.write(", ");
33232            }
33233            self.generate_expression(expr)?;
33234        }
33235        if let Some(each) = &e.each {
33236            self.write_space();
33237            self.write_keyword("EACH");
33238            self.write_space();
33239            self.generate_expression(each)?;
33240        }
33241        self.write(")");
33242        Ok(())
33243    }
33244
33245    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33246        // READ_CSV(this, expressions...)
33247        self.write_keyword("READ_CSV");
33248        self.write("(");
33249        self.generate_expression(&e.this)?;
33250        for expr in &e.expressions {
33251            self.write(", ");
33252            self.generate_expression(expr)?;
33253        }
33254        self.write(")");
33255        Ok(())
33256    }
33257
33258    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33259        // READ_PARQUET(expressions...)
33260        self.write_keyword("READ_PARQUET");
33261        self.write("(");
33262        for (i, expr) in e.expressions.iter().enumerate() {
33263            if i > 0 {
33264                self.write(", ");
33265            }
33266            self.generate_expression(expr)?;
33267        }
33268        self.write(")");
33269        Ok(())
33270    }
33271
33272    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33273        // SEARCH kind FIRST BY this SET expression [USING using]
33274        // or CYCLE this SET expression [USING using]
33275        if e.kind == "CYCLE" {
33276            self.write_keyword("CYCLE");
33277        } else {
33278            self.write_keyword("SEARCH");
33279            self.write_space();
33280            self.write(&e.kind);
33281            self.write_space();
33282            self.write_keyword("FIRST BY");
33283        }
33284        self.write_space();
33285        self.generate_expression(&e.this)?;
33286        self.write_space();
33287        self.write_keyword("SET");
33288        self.write_space();
33289        self.generate_expression(&e.expression)?;
33290        if let Some(using) = &e.using {
33291            self.write_space();
33292            self.write_keyword("USING");
33293            self.write_space();
33294            self.generate_expression(using)?;
33295        }
33296        Ok(())
33297    }
33298
33299    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33300        // REDUCE(this, initial, merge, [finish])
33301        self.write_keyword("REDUCE");
33302        self.write("(");
33303        self.generate_expression(&e.this)?;
33304        if let Some(initial) = &e.initial {
33305            self.write(", ");
33306            self.generate_expression(initial)?;
33307        }
33308        if let Some(merge) = &e.merge {
33309            self.write(", ");
33310            self.generate_expression(merge)?;
33311        }
33312        if let Some(finish) = &e.finish {
33313            self.write(", ");
33314            self.generate_expression(finish)?;
33315        }
33316        self.write(")");
33317        Ok(())
33318    }
33319
33320    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33321        // REFERENCES this (expressions) [options]
33322        self.write_keyword("REFERENCES");
33323        self.write_space();
33324        self.generate_expression(&e.this)?;
33325        if !e.expressions.is_empty() {
33326            self.write(" (");
33327            for (i, expr) in e.expressions.iter().enumerate() {
33328                if i > 0 {
33329                    self.write(", ");
33330                }
33331                self.generate_expression(expr)?;
33332            }
33333            self.write(")");
33334        }
33335        for opt in &e.options {
33336            self.write_space();
33337            self.generate_expression(opt)?;
33338        }
33339        Ok(())
33340    }
33341
33342    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33343        // REFRESH [kind] this
33344        self.write_keyword("REFRESH");
33345        if !e.kind.is_empty() {
33346            self.write_space();
33347            self.write_keyword(&e.kind);
33348        }
33349        self.write_space();
33350        self.generate_expression(&e.this)?;
33351        Ok(())
33352    }
33353
33354    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33355        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33356        self.write_keyword("REFRESH");
33357        self.write_space();
33358        self.write_keyword(&e.method);
33359
33360        if let Some(ref kind) = e.kind {
33361            self.write_space();
33362            self.write_keyword("ON");
33363            self.write_space();
33364            self.write_keyword(kind);
33365
33366            // EVERY n UNIT
33367            if let Some(ref every) = e.every {
33368                self.write_space();
33369                self.write_keyword("EVERY");
33370                self.write_space();
33371                self.generate_expression(every)?;
33372                if let Some(ref unit) = e.unit {
33373                    self.write_space();
33374                    self.write_keyword(unit);
33375                }
33376            }
33377
33378            // STARTS 'datetime'
33379            if let Some(ref starts) = e.starts {
33380                self.write_space();
33381                self.write_keyword("STARTS");
33382                self.write_space();
33383                self.generate_expression(starts)?;
33384            }
33385        }
33386        Ok(())
33387    }
33388
33389    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33390        // REGEXP_COUNT(this, expression, position, parameters)
33391        self.write_keyword("REGEXP_COUNT");
33392        self.write("(");
33393        self.generate_expression(&e.this)?;
33394        self.write(", ");
33395        self.generate_expression(&e.expression)?;
33396        if let Some(position) = &e.position {
33397            self.write(", ");
33398            self.generate_expression(position)?;
33399        }
33400        if let Some(parameters) = &e.parameters {
33401            self.write(", ");
33402            self.generate_expression(parameters)?;
33403        }
33404        self.write(")");
33405        Ok(())
33406    }
33407
33408    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33409        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33410        self.write_keyword("REGEXP_EXTRACT_ALL");
33411        self.write("(");
33412        self.generate_expression(&e.this)?;
33413        self.write(", ");
33414        self.generate_expression(&e.expression)?;
33415        if let Some(group) = &e.group {
33416            self.write(", ");
33417            self.generate_expression(group)?;
33418        }
33419        self.write(")");
33420        Ok(())
33421    }
33422
33423    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33424        // REGEXP_FULL_MATCH(this, expression)
33425        self.write_keyword("REGEXP_FULL_MATCH");
33426        self.write("(");
33427        self.generate_expression(&e.this)?;
33428        self.write(", ");
33429        self.generate_expression(&e.expression)?;
33430        self.write(")");
33431        Ok(())
33432    }
33433
33434    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33435        use crate::dialects::DialectType;
33436        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33437        if matches!(
33438            self.config.dialect,
33439            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33440        ) && e.flag.is_none()
33441        {
33442            self.generate_expression(&e.this)?;
33443            self.write(" ~* ");
33444            self.generate_expression(&e.expression)?;
33445        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33446            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33447            self.write_keyword("REGEXP_LIKE");
33448            self.write("(");
33449            self.generate_expression(&e.this)?;
33450            self.write(", ");
33451            self.generate_expression(&e.expression)?;
33452            self.write(", ");
33453            if let Some(flag) = &e.flag {
33454                self.generate_expression(flag)?;
33455            } else {
33456                self.write("'i'");
33457            }
33458            self.write(")");
33459        } else {
33460            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33461            self.generate_expression(&e.this)?;
33462            self.write_space();
33463            self.write_keyword("REGEXP_ILIKE");
33464            self.write_space();
33465            self.generate_expression(&e.expression)?;
33466            if let Some(flag) = &e.flag {
33467                self.write(", ");
33468                self.generate_expression(flag)?;
33469            }
33470        }
33471        Ok(())
33472    }
33473
33474    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
33475        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
33476        self.write_keyword("REGEXP_INSTR");
33477        self.write("(");
33478        self.generate_expression(&e.this)?;
33479        self.write(", ");
33480        self.generate_expression(&e.expression)?;
33481        if let Some(position) = &e.position {
33482            self.write(", ");
33483            self.generate_expression(position)?;
33484        }
33485        if let Some(occurrence) = &e.occurrence {
33486            self.write(", ");
33487            self.generate_expression(occurrence)?;
33488        }
33489        if let Some(option) = &e.option {
33490            self.write(", ");
33491            self.generate_expression(option)?;
33492        }
33493        if let Some(parameters) = &e.parameters {
33494            self.write(", ");
33495            self.generate_expression(parameters)?;
33496        }
33497        if let Some(group) = &e.group {
33498            self.write(", ");
33499            self.generate_expression(group)?;
33500        }
33501        self.write(")");
33502        Ok(())
33503    }
33504
33505    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
33506        // REGEXP_SPLIT(this, expression, limit)
33507        self.write_keyword("REGEXP_SPLIT");
33508        self.write("(");
33509        self.generate_expression(&e.this)?;
33510        self.write(", ");
33511        self.generate_expression(&e.expression)?;
33512        if let Some(limit) = &e.limit {
33513            self.write(", ");
33514            self.generate_expression(limit)?;
33515        }
33516        self.write(")");
33517        Ok(())
33518    }
33519
33520    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
33521        // REGR_AVGX(this, expression)
33522        self.write_keyword("REGR_AVGX");
33523        self.write("(");
33524        self.generate_expression(&e.this)?;
33525        self.write(", ");
33526        self.generate_expression(&e.expression)?;
33527        self.write(")");
33528        Ok(())
33529    }
33530
33531    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
33532        // REGR_AVGY(this, expression)
33533        self.write_keyword("REGR_AVGY");
33534        self.write("(");
33535        self.generate_expression(&e.this)?;
33536        self.write(", ");
33537        self.generate_expression(&e.expression)?;
33538        self.write(")");
33539        Ok(())
33540    }
33541
33542    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
33543        // REGR_COUNT(this, expression)
33544        self.write_keyword("REGR_COUNT");
33545        self.write("(");
33546        self.generate_expression(&e.this)?;
33547        self.write(", ");
33548        self.generate_expression(&e.expression)?;
33549        self.write(")");
33550        Ok(())
33551    }
33552
33553    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
33554        // REGR_INTERCEPT(this, expression)
33555        self.write_keyword("REGR_INTERCEPT");
33556        self.write("(");
33557        self.generate_expression(&e.this)?;
33558        self.write(", ");
33559        self.generate_expression(&e.expression)?;
33560        self.write(")");
33561        Ok(())
33562    }
33563
33564    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
33565        // REGR_R2(this, expression)
33566        self.write_keyword("REGR_R2");
33567        self.write("(");
33568        self.generate_expression(&e.this)?;
33569        self.write(", ");
33570        self.generate_expression(&e.expression)?;
33571        self.write(")");
33572        Ok(())
33573    }
33574
33575    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
33576        // REGR_SLOPE(this, expression)
33577        self.write_keyword("REGR_SLOPE");
33578        self.write("(");
33579        self.generate_expression(&e.this)?;
33580        self.write(", ");
33581        self.generate_expression(&e.expression)?;
33582        self.write(")");
33583        Ok(())
33584    }
33585
33586    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
33587        // REGR_SXX(this, expression)
33588        self.write_keyword("REGR_SXX");
33589        self.write("(");
33590        self.generate_expression(&e.this)?;
33591        self.write(", ");
33592        self.generate_expression(&e.expression)?;
33593        self.write(")");
33594        Ok(())
33595    }
33596
33597    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33598        // REGR_SXY(this, expression)
33599        self.write_keyword("REGR_SXY");
33600        self.write("(");
33601        self.generate_expression(&e.this)?;
33602        self.write(", ");
33603        self.generate_expression(&e.expression)?;
33604        self.write(")");
33605        Ok(())
33606    }
33607
33608    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33609        // REGR_SYY(this, expression)
33610        self.write_keyword("REGR_SYY");
33611        self.write("(");
33612        self.generate_expression(&e.this)?;
33613        self.write(", ");
33614        self.generate_expression(&e.expression)?;
33615        self.write(")");
33616        Ok(())
33617    }
33618
33619    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33620        // REGR_VALX(this, expression)
33621        self.write_keyword("REGR_VALX");
33622        self.write("(");
33623        self.generate_expression(&e.this)?;
33624        self.write(", ");
33625        self.generate_expression(&e.expression)?;
33626        self.write(")");
33627        Ok(())
33628    }
33629
33630    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33631        // REGR_VALY(this, expression)
33632        self.write_keyword("REGR_VALY");
33633        self.write("(");
33634        self.generate_expression(&e.this)?;
33635        self.write(", ");
33636        self.generate_expression(&e.expression)?;
33637        self.write(")");
33638        Ok(())
33639    }
33640
33641    fn generate_remote_with_connection_model_property(
33642        &mut self,
33643        e: &RemoteWithConnectionModelProperty,
33644    ) -> Result<()> {
33645        // REMOTE WITH CONNECTION this
33646        self.write_keyword("REMOTE WITH CONNECTION");
33647        self.write_space();
33648        self.generate_expression(&e.this)?;
33649        Ok(())
33650    }
33651
33652    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33653        // RENAME COLUMN [IF EXISTS] this TO new_name
33654        self.write_keyword("RENAME COLUMN");
33655        if e.exists {
33656            self.write_space();
33657            self.write_keyword("IF EXISTS");
33658        }
33659        self.write_space();
33660        self.generate_expression(&e.this)?;
33661        if let Some(to) = &e.to {
33662            self.write_space();
33663            self.write_keyword("TO");
33664            self.write_space();
33665            self.generate_expression(to)?;
33666        }
33667        Ok(())
33668    }
33669
33670    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33671        // REPLACE PARTITION expression [FROM source]
33672        self.write_keyword("REPLACE PARTITION");
33673        self.write_space();
33674        self.generate_expression(&e.expression)?;
33675        if let Some(source) = &e.source {
33676            self.write_space();
33677            self.write_keyword("FROM");
33678            self.write_space();
33679            self.generate_expression(source)?;
33680        }
33681        Ok(())
33682    }
33683
33684    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33685        // RETURNING expressions [INTO into]
33686        // TSQL and Fabric use OUTPUT instead of RETURNING
33687        let keyword = match self.config.dialect {
33688            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33689            _ => "RETURNING",
33690        };
33691        self.write_keyword(keyword);
33692        self.write_space();
33693        for (i, expr) in e.expressions.iter().enumerate() {
33694            if i > 0 {
33695                self.write(", ");
33696            }
33697            self.generate_expression(expr)?;
33698        }
33699        if let Some(into) = &e.into {
33700            self.write_space();
33701            self.write_keyword("INTO");
33702            self.write_space();
33703            self.generate_expression(into)?;
33704        }
33705        Ok(())
33706    }
33707
33708    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33709        // OUTPUT expressions [INTO into_table]
33710        self.write_space();
33711        self.write_keyword("OUTPUT");
33712        self.write_space();
33713        for (i, expr) in output.columns.iter().enumerate() {
33714            if i > 0 {
33715                self.write(", ");
33716            }
33717            self.generate_expression(expr)?;
33718        }
33719        if let Some(into_table) = &output.into_table {
33720            self.write_space();
33721            self.write_keyword("INTO");
33722            self.write_space();
33723            self.generate_expression(into_table)?;
33724        }
33725        Ok(())
33726    }
33727
33728    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33729        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33730        self.write_keyword("RETURNS");
33731        if e.is_table.is_some() {
33732            self.write_space();
33733            self.write_keyword("TABLE");
33734        }
33735        if let Some(table) = &e.table {
33736            self.write_space();
33737            self.generate_expression(table)?;
33738        } else if let Some(this) = &e.this {
33739            self.write_space();
33740            self.generate_expression(this)?;
33741        }
33742        if e.null.is_some() {
33743            self.write_space();
33744            self.write_keyword("NULL ON NULL INPUT");
33745        }
33746        Ok(())
33747    }
33748
33749    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33750        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33751        self.write_keyword("ROLLBACK");
33752
33753        // TSQL always uses ROLLBACK TRANSACTION
33754        if e.this.is_none()
33755            && matches!(
33756                self.config.dialect,
33757                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33758            )
33759        {
33760            self.write_space();
33761            self.write_keyword("TRANSACTION");
33762        }
33763
33764        // Check if this has TRANSACTION keyword or transaction name
33765        if let Some(this) = &e.this {
33766            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33767            let is_transaction_marker = matches!(
33768                this.as_ref(),
33769                Expression::Identifier(id) if id.name == "TRANSACTION"
33770            );
33771
33772            self.write_space();
33773            self.write_keyword("TRANSACTION");
33774
33775            // If it's a real transaction name, output it
33776            if !is_transaction_marker {
33777                self.write_space();
33778                self.generate_expression(this)?;
33779            }
33780        }
33781
33782        // Output TO savepoint
33783        if let Some(savepoint) = &e.savepoint {
33784            self.write_space();
33785            self.write_keyword("TO");
33786            self.write_space();
33787            self.generate_expression(savepoint)?;
33788        }
33789        Ok(())
33790    }
33791
33792    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33793        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33794        if e.expressions.is_empty() {
33795            self.write_keyword("WITH ROLLUP");
33796        } else {
33797            self.write_keyword("ROLLUP");
33798            self.write("(");
33799            for (i, expr) in e.expressions.iter().enumerate() {
33800                if i > 0 {
33801                    self.write(", ");
33802                }
33803                self.generate_expression(expr)?;
33804            }
33805            self.write(")");
33806        }
33807        Ok(())
33808    }
33809
33810    fn generate_row_format_delimited_property(
33811        &mut self,
33812        e: &RowFormatDelimitedProperty,
33813    ) -> Result<()> {
33814        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33815        self.write_keyword("ROW FORMAT DELIMITED");
33816        if let Some(fields) = &e.fields {
33817            self.write_space();
33818            self.write_keyword("FIELDS TERMINATED BY");
33819            self.write_space();
33820            self.generate_expression(fields)?;
33821        }
33822        if let Some(escaped) = &e.escaped {
33823            self.write_space();
33824            self.write_keyword("ESCAPED BY");
33825            self.write_space();
33826            self.generate_expression(escaped)?;
33827        }
33828        if let Some(items) = &e.collection_items {
33829            self.write_space();
33830            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33831            self.write_space();
33832            self.generate_expression(items)?;
33833        }
33834        if let Some(keys) = &e.map_keys {
33835            self.write_space();
33836            self.write_keyword("MAP KEYS TERMINATED BY");
33837            self.write_space();
33838            self.generate_expression(keys)?;
33839        }
33840        if let Some(lines) = &e.lines {
33841            self.write_space();
33842            self.write_keyword("LINES TERMINATED BY");
33843            self.write_space();
33844            self.generate_expression(lines)?;
33845        }
33846        if let Some(null) = &e.null {
33847            self.write_space();
33848            self.write_keyword("NULL DEFINED AS");
33849            self.write_space();
33850            self.generate_expression(null)?;
33851        }
33852        if let Some(serde) = &e.serde {
33853            self.write_space();
33854            self.generate_expression(serde)?;
33855        }
33856        Ok(())
33857    }
33858
33859    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33860        // ROW FORMAT this
33861        self.write_keyword("ROW FORMAT");
33862        self.write_space();
33863        self.generate_expression(&e.this)?;
33864        Ok(())
33865    }
33866
33867    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33868        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33869        self.write_keyword("ROW FORMAT SERDE");
33870        self.write_space();
33871        self.generate_expression(&e.this)?;
33872        if let Some(props) = &e.serde_properties {
33873            self.write_space();
33874            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33875            self.generate_expression(props)?;
33876        }
33877        Ok(())
33878    }
33879
33880    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33881        // SHA2(this, length)
33882        self.write_keyword("SHA2");
33883        self.write("(");
33884        self.generate_expression(&e.this)?;
33885        if let Some(length) = e.length {
33886            self.write(", ");
33887            self.write(&length.to_string());
33888        }
33889        self.write(")");
33890        Ok(())
33891    }
33892
33893    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33894        // SHA2_DIGEST(this, length)
33895        self.write_keyword("SHA2_DIGEST");
33896        self.write("(");
33897        self.generate_expression(&e.this)?;
33898        if let Some(length) = e.length {
33899            self.write(", ");
33900            self.write(&length.to_string());
33901        }
33902        self.write(")");
33903        Ok(())
33904    }
33905
33906    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33907        let name = if matches!(
33908            self.config.dialect,
33909            Some(crate::dialects::DialectType::Spark)
33910                | Some(crate::dialects::DialectType::Databricks)
33911        ) {
33912            "TRY_ADD"
33913        } else {
33914            "SAFE_ADD"
33915        };
33916        self.write_keyword(name);
33917        self.write("(");
33918        self.generate_expression(&e.this)?;
33919        self.write(", ");
33920        self.generate_expression(&e.expression)?;
33921        self.write(")");
33922        Ok(())
33923    }
33924
33925    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33926        // SAFE_DIVIDE(this, expression)
33927        self.write_keyword("SAFE_DIVIDE");
33928        self.write("(");
33929        self.generate_expression(&e.this)?;
33930        self.write(", ");
33931        self.generate_expression(&e.expression)?;
33932        self.write(")");
33933        Ok(())
33934    }
33935
33936    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33937        let name = if matches!(
33938            self.config.dialect,
33939            Some(crate::dialects::DialectType::Spark)
33940                | Some(crate::dialects::DialectType::Databricks)
33941        ) {
33942            "TRY_MULTIPLY"
33943        } else {
33944            "SAFE_MULTIPLY"
33945        };
33946        self.write_keyword(name);
33947        self.write("(");
33948        self.generate_expression(&e.this)?;
33949        self.write(", ");
33950        self.generate_expression(&e.expression)?;
33951        self.write(")");
33952        Ok(())
33953    }
33954
33955    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33956        let name = if matches!(
33957            self.config.dialect,
33958            Some(crate::dialects::DialectType::Spark)
33959                | Some(crate::dialects::DialectType::Databricks)
33960        ) {
33961            "TRY_SUBTRACT"
33962        } else {
33963            "SAFE_SUBTRACT"
33964        };
33965        self.write_keyword(name);
33966        self.write("(");
33967        self.generate_expression(&e.this)?;
33968        self.write(", ");
33969        self.generate_expression(&e.expression)?;
33970        self.write(")");
33971        Ok(())
33972    }
33973
33974    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33975    /// METHOD (size UNIT) [REPEATABLE (seed)]
33976    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33977        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33978        if matches!(sample.method, SampleMethod::Bucket) {
33979            self.write(" (");
33980            self.write_keyword("BUCKET");
33981            self.write_space();
33982            if let Some(ref num) = sample.bucket_numerator {
33983                self.generate_expression(num)?;
33984            }
33985            self.write_space();
33986            self.write_keyword("OUT OF");
33987            self.write_space();
33988            if let Some(ref denom) = sample.bucket_denominator {
33989                self.generate_expression(denom)?;
33990            }
33991            if let Some(ref field) = sample.bucket_field {
33992                self.write_space();
33993                self.write_keyword("ON");
33994                self.write_space();
33995                self.generate_expression(field)?;
33996            }
33997            self.write(")");
33998            return Ok(());
33999        }
34000
34001        // Output method name if explicitly specified, or for dialects that always require it
34002        let is_snowflake = matches!(
34003            self.config.dialect,
34004            Some(crate::dialects::DialectType::Snowflake)
34005        );
34006        let is_postgres = matches!(
34007            self.config.dialect,
34008            Some(crate::dialects::DialectType::PostgreSQL)
34009                | Some(crate::dialects::DialectType::Redshift)
34010        );
34011        // Databricks and Spark don't output method names
34012        let is_databricks = matches!(
34013            self.config.dialect,
34014            Some(crate::dialects::DialectType::Databricks)
34015        );
34016        let is_spark = matches!(
34017            self.config.dialect,
34018            Some(crate::dialects::DialectType::Spark)
34019        );
34020        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34021        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34022        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34023        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34024            self.write_space();
34025            if !sample.explicit_method && (is_snowflake || force_method) {
34026                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34027                self.write_keyword("BERNOULLI");
34028            } else {
34029                match sample.method {
34030                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34031                    SampleMethod::System => self.write_keyword("SYSTEM"),
34032                    SampleMethod::Block => self.write_keyword("BLOCK"),
34033                    SampleMethod::Row => self.write_keyword("ROW"),
34034                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34035                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34036                    SampleMethod::Bucket => {} // handled above
34037                }
34038            }
34039        }
34040
34041        // Output size, with or without parentheses depending on dialect
34042        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34043        if emit_size_no_parens {
34044            self.write_space();
34045            match &sample.size {
34046                Expression::Tuple(tuple) => {
34047                    for (i, expr) in tuple.expressions.iter().enumerate() {
34048                        if i > 0 {
34049                            self.write(", ");
34050                        }
34051                        self.generate_expression(expr)?;
34052                    }
34053                }
34054                expr => self.generate_expression(expr)?,
34055            }
34056        } else {
34057            self.write(" (");
34058            self.generate_expression(&sample.size)?;
34059        }
34060
34061        // Determine unit
34062        let is_rows_method = matches!(
34063            sample.method,
34064            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34065        );
34066        let is_percent = matches!(
34067            sample.method,
34068            SampleMethod::Percent
34069                | SampleMethod::System
34070                | SampleMethod::Bernoulli
34071                | SampleMethod::Block
34072        );
34073
34074        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34075        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34076        // For Databricks and Spark, always output PERCENT for percentage samples.
34077        let is_presto = matches!(
34078            self.config.dialect,
34079            Some(crate::dialects::DialectType::Presto)
34080                | Some(crate::dialects::DialectType::Trino)
34081                | Some(crate::dialects::DialectType::Athena)
34082        );
34083        let should_output_unit = if is_databricks || is_spark {
34084            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34085            is_percent || is_rows_method || sample.unit_after_size
34086        } else if is_snowflake || is_postgres || is_presto {
34087            sample.unit_after_size
34088        } else {
34089            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34090        };
34091
34092        if should_output_unit {
34093            self.write_space();
34094            if sample.is_percent {
34095                self.write_keyword("PERCENT");
34096            } else if is_rows_method && !sample.unit_after_size {
34097                self.write_keyword("ROWS");
34098            } else if sample.unit_after_size {
34099                match sample.method {
34100                    SampleMethod::Percent
34101                    | SampleMethod::System
34102                    | SampleMethod::Bernoulli
34103                    | SampleMethod::Block => {
34104                        self.write_keyword("PERCENT");
34105                    }
34106                    SampleMethod::Row | SampleMethod::Reservoir => {
34107                        self.write_keyword("ROWS");
34108                    }
34109                    _ => self.write_keyword("ROWS"),
34110                }
34111            } else {
34112                self.write_keyword("PERCENT");
34113            }
34114        }
34115
34116        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34117            if let Some(ref offset) = sample.offset {
34118                self.write_space();
34119                self.write_keyword("OFFSET");
34120                self.write_space();
34121                self.generate_expression(offset)?;
34122            }
34123        }
34124        if !emit_size_no_parens {
34125            self.write(")");
34126        }
34127
34128        Ok(())
34129    }
34130
34131    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34132        // SAMPLE this (ClickHouse uses SAMPLE BY)
34133        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34134            self.write_keyword("SAMPLE BY");
34135        } else {
34136            self.write_keyword("SAMPLE");
34137        }
34138        self.write_space();
34139        self.generate_expression(&e.this)?;
34140        Ok(())
34141    }
34142
34143    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34144        // this (expressions...)
34145        if let Some(this) = &e.this {
34146            self.generate_expression(this)?;
34147        }
34148        if !e.expressions.is_empty() {
34149            // Add space before column list if there's a preceding expression
34150            if e.this.is_some() {
34151                self.write_space();
34152            }
34153            self.write("(");
34154            for (i, expr) in e.expressions.iter().enumerate() {
34155                if i > 0 {
34156                    self.write(", ");
34157                }
34158                self.generate_expression(expr)?;
34159            }
34160            self.write(")");
34161        }
34162        Ok(())
34163    }
34164
34165    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34166        // COMMENT this
34167        self.write_keyword("COMMENT");
34168        self.write_space();
34169        self.generate_expression(&e.this)?;
34170        Ok(())
34171    }
34172
34173    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34174        // [this::]expression
34175        if let Some(this) = &e.this {
34176            self.generate_expression(this)?;
34177            self.write("::");
34178        }
34179        self.generate_expression(&e.expression)?;
34180        Ok(())
34181    }
34182
34183    fn generate_search(&mut self, e: &Search) -> Result<()> {
34184        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34185        self.write_keyword("SEARCH");
34186        self.write("(");
34187        self.generate_expression(&e.this)?;
34188        self.write(", ");
34189        self.generate_expression(&e.expression)?;
34190        if let Some(json_scope) = &e.json_scope {
34191            self.write(", ");
34192            self.generate_expression(json_scope)?;
34193        }
34194        if let Some(analyzer) = &e.analyzer {
34195            self.write(", ");
34196            self.generate_expression(analyzer)?;
34197        }
34198        if let Some(analyzer_options) = &e.analyzer_options {
34199            self.write(", ");
34200            self.generate_expression(analyzer_options)?;
34201        }
34202        if let Some(search_mode) = &e.search_mode {
34203            self.write(", ");
34204            self.generate_expression(search_mode)?;
34205        }
34206        self.write(")");
34207        Ok(())
34208    }
34209
34210    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34211        // SEARCH_IP(this, expression)
34212        self.write_keyword("SEARCH_IP");
34213        self.write("(");
34214        self.generate_expression(&e.this)?;
34215        self.write(", ");
34216        self.generate_expression(&e.expression)?;
34217        self.write(")");
34218        Ok(())
34219    }
34220
34221    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34222        // SECURITY this
34223        self.write_keyword("SECURITY");
34224        self.write_space();
34225        self.generate_expression(&e.this)?;
34226        Ok(())
34227    }
34228
34229    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34230        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34231        self.write("SEMANTIC_VIEW(");
34232
34233        if self.config.pretty {
34234            // Pretty print: each clause on its own line
34235            self.write_newline();
34236            self.indent_level += 1;
34237            self.write_indent();
34238            self.generate_expression(&e.this)?;
34239
34240            if let Some(metrics) = &e.metrics {
34241                self.write_newline();
34242                self.write_indent();
34243                self.write_keyword("METRICS");
34244                self.write_space();
34245                self.generate_semantic_view_tuple(metrics)?;
34246            }
34247            if let Some(dimensions) = &e.dimensions {
34248                self.write_newline();
34249                self.write_indent();
34250                self.write_keyword("DIMENSIONS");
34251                self.write_space();
34252                self.generate_semantic_view_tuple(dimensions)?;
34253            }
34254            if let Some(facts) = &e.facts {
34255                self.write_newline();
34256                self.write_indent();
34257                self.write_keyword("FACTS");
34258                self.write_space();
34259                self.generate_semantic_view_tuple(facts)?;
34260            }
34261            if let Some(where_) = &e.where_ {
34262                self.write_newline();
34263                self.write_indent();
34264                self.write_keyword("WHERE");
34265                self.write_space();
34266                self.generate_expression(where_)?;
34267            }
34268            self.write_newline();
34269            self.indent_level -= 1;
34270            self.write_indent();
34271        } else {
34272            // Compact: all on one line
34273            self.generate_expression(&e.this)?;
34274            if let Some(metrics) = &e.metrics {
34275                self.write_space();
34276                self.write_keyword("METRICS");
34277                self.write_space();
34278                self.generate_semantic_view_tuple(metrics)?;
34279            }
34280            if let Some(dimensions) = &e.dimensions {
34281                self.write_space();
34282                self.write_keyword("DIMENSIONS");
34283                self.write_space();
34284                self.generate_semantic_view_tuple(dimensions)?;
34285            }
34286            if let Some(facts) = &e.facts {
34287                self.write_space();
34288                self.write_keyword("FACTS");
34289                self.write_space();
34290                self.generate_semantic_view_tuple(facts)?;
34291            }
34292            if let Some(where_) = &e.where_ {
34293                self.write_space();
34294                self.write_keyword("WHERE");
34295                self.write_space();
34296                self.generate_expression(where_)?;
34297            }
34298        }
34299        self.write(")");
34300        Ok(())
34301    }
34302
34303    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34304    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34305        if let Expression::Tuple(t) = expr {
34306            for (i, e) in t.expressions.iter().enumerate() {
34307                if i > 0 {
34308                    self.write(", ");
34309                }
34310                self.generate_expression(e)?;
34311            }
34312        } else {
34313            self.generate_expression(expr)?;
34314        }
34315        Ok(())
34316    }
34317
34318    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34319        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34320        if let Some(start) = &e.start {
34321            self.write_keyword("START WITH");
34322            self.write_space();
34323            self.generate_expression(start)?;
34324        }
34325        if let Some(increment) = &e.increment {
34326            self.write_space();
34327            self.write_keyword("INCREMENT BY");
34328            self.write_space();
34329            self.generate_expression(increment)?;
34330        }
34331        if let Some(minvalue) = &e.minvalue {
34332            self.write_space();
34333            self.write_keyword("MINVALUE");
34334            self.write_space();
34335            self.generate_expression(minvalue)?;
34336        }
34337        if let Some(maxvalue) = &e.maxvalue {
34338            self.write_space();
34339            self.write_keyword("MAXVALUE");
34340            self.write_space();
34341            self.generate_expression(maxvalue)?;
34342        }
34343        if let Some(cache) = &e.cache {
34344            self.write_space();
34345            self.write_keyword("CACHE");
34346            self.write_space();
34347            self.generate_expression(cache)?;
34348        }
34349        if let Some(owned) = &e.owned {
34350            self.write_space();
34351            self.write_keyword("OWNED BY");
34352            self.write_space();
34353            self.generate_expression(owned)?;
34354        }
34355        for opt in &e.options {
34356            self.write_space();
34357            self.generate_expression(opt)?;
34358        }
34359        Ok(())
34360    }
34361
34362    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34363        // [WITH] SERDEPROPERTIES (expressions)
34364        if e.with_.is_some() {
34365            self.write_keyword("WITH");
34366            self.write_space();
34367        }
34368        self.write_keyword("SERDEPROPERTIES");
34369        self.write(" (");
34370        for (i, expr) in e.expressions.iter().enumerate() {
34371            if i > 0 {
34372                self.write(", ");
34373            }
34374            // Generate key=value without spaces around =
34375            match expr {
34376                Expression::Eq(eq) => {
34377                    self.generate_expression(&eq.left)?;
34378                    self.write("=");
34379                    self.generate_expression(&eq.right)?;
34380                }
34381                _ => self.generate_expression(expr)?,
34382            }
34383        }
34384        self.write(")");
34385        Ok(())
34386    }
34387
34388    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34389        // @@[kind.]this
34390        self.write("@@");
34391        if let Some(kind) = &e.kind {
34392            self.write(kind);
34393            self.write(".");
34394        }
34395        self.generate_expression(&e.this)?;
34396        Ok(())
34397    }
34398
34399    fn generate_set(&mut self, e: &Set) -> Result<()> {
34400        // SET/UNSET [TAG] expressions
34401        if e.unset.is_some() {
34402            self.write_keyword("UNSET");
34403        } else {
34404            self.write_keyword("SET");
34405        }
34406        if e.tag.is_some() {
34407            self.write_space();
34408            self.write_keyword("TAG");
34409        }
34410        if !e.expressions.is_empty() {
34411            self.write_space();
34412            for (i, expr) in e.expressions.iter().enumerate() {
34413                if i > 0 {
34414                    self.write(", ");
34415                }
34416                self.generate_expression(expr)?;
34417            }
34418        }
34419        Ok(())
34420    }
34421
34422    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34423        // SET this or SETCONFIG this
34424        self.write_keyword("SET");
34425        self.write_space();
34426        self.generate_expression(&e.this)?;
34427        Ok(())
34428    }
34429
34430    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34431        // [kind] name = value
34432        if let Some(kind) = &e.kind {
34433            self.write_keyword(kind);
34434            self.write_space();
34435        }
34436        self.generate_expression(&e.name)?;
34437        self.write(" = ");
34438        self.generate_expression(&e.value)?;
34439        Ok(())
34440    }
34441
34442    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34443        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34444        if let Some(with_) = &e.with_ {
34445            self.generate_expression(with_)?;
34446            self.write_space();
34447        }
34448        self.generate_expression(&e.this)?;
34449        self.write_space();
34450        // kind should be UNION, INTERSECT, EXCEPT, etc.
34451        if let Some(kind) = &e.kind {
34452            self.write_keyword(kind);
34453        }
34454        if e.distinct {
34455            self.write_space();
34456            self.write_keyword("DISTINCT");
34457        } else {
34458            self.write_space();
34459            self.write_keyword("ALL");
34460        }
34461        if e.by_name.is_some() {
34462            self.write_space();
34463            self.write_keyword("BY NAME");
34464        }
34465        self.write_space();
34466        self.generate_expression(&e.expression)?;
34467        Ok(())
34468    }
34469
34470    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
34471        // SET or MULTISET
34472        if e.multi.is_some() {
34473            self.write_keyword("MULTISET");
34474        } else {
34475            self.write_keyword("SET");
34476        }
34477        Ok(())
34478    }
34479
34480    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
34481        // SETTINGS expressions
34482        self.write_keyword("SETTINGS");
34483        if self.config.pretty && e.expressions.len() > 1 {
34484            // Pretty print: each setting on its own line, indented
34485            self.indent_level += 1;
34486            for (i, expr) in e.expressions.iter().enumerate() {
34487                if i > 0 {
34488                    self.write(",");
34489                }
34490                self.write_newline();
34491                self.write_indent();
34492                self.generate_expression(expr)?;
34493            }
34494            self.indent_level -= 1;
34495        } else {
34496            self.write_space();
34497            for (i, expr) in e.expressions.iter().enumerate() {
34498                if i > 0 {
34499                    self.write(", ");
34500                }
34501                self.generate_expression(expr)?;
34502            }
34503        }
34504        Ok(())
34505    }
34506
34507    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
34508        // SHARING = this
34509        self.write_keyword("SHARING");
34510        if let Some(this) = &e.this {
34511            self.write(" = ");
34512            self.generate_expression(this)?;
34513        }
34514        Ok(())
34515    }
34516
34517    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
34518        // Python array slicing: begin:end:step
34519        if let Some(begin) = &e.this {
34520            self.generate_expression(begin)?;
34521        }
34522        self.write(":");
34523        if let Some(end) = &e.expression {
34524            self.generate_expression(end)?;
34525        }
34526        if let Some(step) = &e.step {
34527            self.write(":");
34528            self.generate_expression(step)?;
34529        }
34530        Ok(())
34531    }
34532
34533    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
34534        // SORT_ARRAY(this, asc)
34535        self.write_keyword("SORT_ARRAY");
34536        self.write("(");
34537        self.generate_expression(&e.this)?;
34538        if let Some(asc) = &e.asc {
34539            self.write(", ");
34540            self.generate_expression(asc)?;
34541        }
34542        self.write(")");
34543        Ok(())
34544    }
34545
34546    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
34547        // SORT BY expressions
34548        self.write_keyword("SORT BY");
34549        self.write_space();
34550        for (i, expr) in e.expressions.iter().enumerate() {
34551            if i > 0 {
34552                self.write(", ");
34553            }
34554            self.generate_ordered(expr)?;
34555        }
34556        Ok(())
34557    }
34558
34559    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
34560        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
34561        if e.compound.is_some() {
34562            self.write_keyword("COMPOUND");
34563            self.write_space();
34564        }
34565        self.write_keyword("SORTKEY");
34566        self.write("(");
34567        // If this is a Tuple, unwrap its contents to avoid double parentheses
34568        if let Expression::Tuple(t) = e.this.as_ref() {
34569            for (i, expr) in t.expressions.iter().enumerate() {
34570                if i > 0 {
34571                    self.write(", ");
34572                }
34573                self.generate_expression(expr)?;
34574            }
34575        } else {
34576            self.generate_expression(&e.this)?;
34577        }
34578        self.write(")");
34579        Ok(())
34580    }
34581
34582    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
34583        // SPLIT_PART(this, delimiter, part_index)
34584        self.write_keyword("SPLIT_PART");
34585        self.write("(");
34586        self.generate_expression(&e.this)?;
34587        if let Some(delimiter) = &e.delimiter {
34588            self.write(", ");
34589            self.generate_expression(delimiter)?;
34590        }
34591        if let Some(part_index) = &e.part_index {
34592            self.write(", ");
34593            self.generate_expression(part_index)?;
34594        }
34595        self.write(")");
34596        Ok(())
34597    }
34598
34599    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34600        // READS SQL DATA or MODIFIES SQL DATA, etc.
34601        self.generate_expression(&e.this)?;
34602        Ok(())
34603    }
34604
34605    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34606        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34607        self.write_keyword("SQL SECURITY");
34608        self.write_space();
34609        self.generate_expression(&e.this)?;
34610        Ok(())
34611    }
34612
34613    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34614        // ST_DISTANCE(this, expression, [use_spheroid])
34615        self.write_keyword("ST_DISTANCE");
34616        self.write("(");
34617        self.generate_expression(&e.this)?;
34618        self.write(", ");
34619        self.generate_expression(&e.expression)?;
34620        if let Some(use_spheroid) = &e.use_spheroid {
34621            self.write(", ");
34622            self.generate_expression(use_spheroid)?;
34623        }
34624        self.write(")");
34625        Ok(())
34626    }
34627
34628    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34629        // ST_POINT(this, expression)
34630        self.write_keyword("ST_POINT");
34631        self.write("(");
34632        self.generate_expression(&e.this)?;
34633        self.write(", ");
34634        self.generate_expression(&e.expression)?;
34635        self.write(")");
34636        Ok(())
34637    }
34638
34639    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34640        // IMMUTABLE, STABLE, VOLATILE
34641        self.generate_expression(&e.this)?;
34642        Ok(())
34643    }
34644
34645    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34646        // STANDARD_HASH(this, [expression])
34647        self.write_keyword("STANDARD_HASH");
34648        self.write("(");
34649        self.generate_expression(&e.this)?;
34650        if let Some(expression) = &e.expression {
34651            self.write(", ");
34652            self.generate_expression(expression)?;
34653        }
34654        self.write(")");
34655        Ok(())
34656    }
34657
34658    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34659        // STORED BY this
34660        self.write_keyword("STORED BY");
34661        self.write_space();
34662        self.generate_expression(&e.this)?;
34663        Ok(())
34664    }
34665
34666    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34667        // STRPOS(this, substr) or STRPOS(this, substr, position)
34668        // Different dialects have different function names
34669        use crate::dialects::DialectType;
34670        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34671            // Snowflake: CHARINDEX(substr, str[, position])
34672            self.write_keyword("CHARINDEX");
34673            self.write("(");
34674            if let Some(substr) = &e.substr {
34675                self.generate_expression(substr)?;
34676                self.write(", ");
34677            }
34678            self.generate_expression(&e.this)?;
34679            if let Some(position) = &e.position {
34680                self.write(", ");
34681                self.generate_expression(position)?;
34682            }
34683            self.write(")");
34684        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34685            self.write_keyword("POSITION");
34686            self.write("(");
34687            self.generate_expression(&e.this)?;
34688            if let Some(substr) = &e.substr {
34689                self.write(", ");
34690                self.generate_expression(substr)?;
34691            }
34692            if let Some(position) = &e.position {
34693                self.write(", ");
34694                self.generate_expression(position)?;
34695            }
34696            if let Some(occurrence) = &e.occurrence {
34697                self.write(", ");
34698                self.generate_expression(occurrence)?;
34699            }
34700            self.write(")");
34701        } else if matches!(
34702            self.config.dialect,
34703            Some(DialectType::SQLite)
34704                | Some(DialectType::Oracle)
34705                | Some(DialectType::BigQuery)
34706                | Some(DialectType::Teradata)
34707        ) {
34708            self.write_keyword("INSTR");
34709            self.write("(");
34710            self.generate_expression(&e.this)?;
34711            if let Some(substr) = &e.substr {
34712                self.write(", ");
34713                self.generate_expression(substr)?;
34714            }
34715            if let Some(position) = &e.position {
34716                self.write(", ");
34717                self.generate_expression(position)?;
34718            } else if e.occurrence.is_some() {
34719                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34720                // Default start position is 1
34721                self.write(", 1");
34722            }
34723            if let Some(occurrence) = &e.occurrence {
34724                self.write(", ");
34725                self.generate_expression(occurrence)?;
34726            }
34727            self.write(")");
34728        } else if matches!(
34729            self.config.dialect,
34730            Some(DialectType::MySQL)
34731                | Some(DialectType::SingleStore)
34732                | Some(DialectType::Doris)
34733                | Some(DialectType::StarRocks)
34734                | Some(DialectType::Hive)
34735                | Some(DialectType::Spark)
34736                | Some(DialectType::Databricks)
34737        ) {
34738            // LOCATE(substr, str[, position]) - substr first
34739            self.write_keyword("LOCATE");
34740            self.write("(");
34741            if let Some(substr) = &e.substr {
34742                self.generate_expression(substr)?;
34743                self.write(", ");
34744            }
34745            self.generate_expression(&e.this)?;
34746            if let Some(position) = &e.position {
34747                self.write(", ");
34748                self.generate_expression(position)?;
34749            }
34750            self.write(")");
34751        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34752            // CHARINDEX(substr, str[, position])
34753            self.write_keyword("CHARINDEX");
34754            self.write("(");
34755            if let Some(substr) = &e.substr {
34756                self.generate_expression(substr)?;
34757                self.write(", ");
34758            }
34759            self.generate_expression(&e.this)?;
34760            if let Some(position) = &e.position {
34761                self.write(", ");
34762                self.generate_expression(position)?;
34763            }
34764            self.write(")");
34765        } else if matches!(
34766            self.config.dialect,
34767            Some(DialectType::PostgreSQL)
34768                | Some(DialectType::Materialize)
34769                | Some(DialectType::RisingWave)
34770                | Some(DialectType::Redshift)
34771        ) {
34772            // POSITION(substr IN str) syntax
34773            self.write_keyword("POSITION");
34774            self.write("(");
34775            if let Some(substr) = &e.substr {
34776                self.generate_expression(substr)?;
34777                self.write(" IN ");
34778            }
34779            self.generate_expression(&e.this)?;
34780            self.write(")");
34781        } else {
34782            self.write_keyword("STRPOS");
34783            self.write("(");
34784            self.generate_expression(&e.this)?;
34785            if let Some(substr) = &e.substr {
34786                self.write(", ");
34787                self.generate_expression(substr)?;
34788            }
34789            if let Some(position) = &e.position {
34790                self.write(", ");
34791                self.generate_expression(position)?;
34792            }
34793            if let Some(occurrence) = &e.occurrence {
34794                self.write(", ");
34795                self.generate_expression(occurrence)?;
34796            }
34797            self.write(")");
34798        }
34799        Ok(())
34800    }
34801
34802    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34803        match self.config.dialect {
34804            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34805                // TO_DATE(this, java_format)
34806                self.write_keyword("TO_DATE");
34807                self.write("(");
34808                self.generate_expression(&e.this)?;
34809                if let Some(format) = &e.format {
34810                    self.write(", '");
34811                    self.write(&Self::strftime_to_java_format(format));
34812                    self.write("'");
34813                }
34814                self.write(")");
34815            }
34816            Some(DialectType::DuckDB) => {
34817                // CAST(STRPTIME(this, format) AS DATE)
34818                self.write_keyword("CAST");
34819                self.write("(");
34820                self.write_keyword("STRPTIME");
34821                self.write("(");
34822                self.generate_expression(&e.this)?;
34823                if let Some(format) = &e.format {
34824                    self.write(", '");
34825                    self.write(format);
34826                    self.write("'");
34827                }
34828                self.write(")");
34829                self.write_keyword(" AS ");
34830                self.write_keyword("DATE");
34831                self.write(")");
34832            }
34833            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34834                // TO_DATE(this, pg_format)
34835                self.write_keyword("TO_DATE");
34836                self.write("(");
34837                self.generate_expression(&e.this)?;
34838                if let Some(format) = &e.format {
34839                    self.write(", '");
34840                    self.write(&Self::strftime_to_postgres_format(format));
34841                    self.write("'");
34842                }
34843                self.write(")");
34844            }
34845            Some(DialectType::BigQuery) => {
34846                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34847                self.write_keyword("PARSE_DATE");
34848                self.write("(");
34849                if let Some(format) = &e.format {
34850                    self.write("'");
34851                    self.write(format);
34852                    self.write("'");
34853                    self.write(", ");
34854                }
34855                self.generate_expression(&e.this)?;
34856                self.write(")");
34857            }
34858            Some(DialectType::Teradata) => {
34859                // CAST(this AS DATE FORMAT 'teradata_fmt')
34860                self.write_keyword("CAST");
34861                self.write("(");
34862                self.generate_expression(&e.this)?;
34863                self.write_keyword(" AS ");
34864                self.write_keyword("DATE");
34865                if let Some(format) = &e.format {
34866                    self.write_keyword(" FORMAT ");
34867                    self.write("'");
34868                    self.write(&Self::strftime_to_teradata_format(format));
34869                    self.write("'");
34870                }
34871                self.write(")");
34872            }
34873            _ => {
34874                // STR_TO_DATE(this, format) - MySQL default
34875                self.write_keyword("STR_TO_DATE");
34876                self.write("(");
34877                self.generate_expression(&e.this)?;
34878                if let Some(format) = &e.format {
34879                    self.write(", '");
34880                    self.write(format);
34881                    self.write("'");
34882                }
34883                self.write(")");
34884            }
34885        }
34886        Ok(())
34887    }
34888
34889    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34890    fn strftime_to_teradata_format(fmt: &str) -> String {
34891        let mut result = String::with_capacity(fmt.len() * 2);
34892        let bytes = fmt.as_bytes();
34893        let len = bytes.len();
34894        let mut i = 0;
34895        while i < len {
34896            if bytes[i] == b'%' && i + 1 < len {
34897                let replacement = match bytes[i + 1] {
34898                    b'Y' => "YYYY",
34899                    b'y' => "YY",
34900                    b'm' => "MM",
34901                    b'B' => "MMMM",
34902                    b'b' => "MMM",
34903                    b'd' => "DD",
34904                    b'j' => "DDD",
34905                    b'H' => "HH",
34906                    b'M' => "MI",
34907                    b'S' => "SS",
34908                    b'f' => "SSSSSS",
34909                    b'A' => "EEEE",
34910                    b'a' => "EEE",
34911                    _ => {
34912                        result.push('%');
34913                        i += 1;
34914                        continue;
34915                    }
34916                };
34917                result.push_str(replacement);
34918                i += 2;
34919            } else {
34920                result.push(bytes[i] as char);
34921                i += 1;
34922            }
34923        }
34924        result
34925    }
34926
34927    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34928    /// Public static version for use by other modules
34929    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34930        Self::strftime_to_java_format(fmt)
34931    }
34932
34933    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34934    fn strftime_to_java_format(fmt: &str) -> String {
34935        let mut result = String::with_capacity(fmt.len() * 2);
34936        let bytes = fmt.as_bytes();
34937        let len = bytes.len();
34938        let mut i = 0;
34939        while i < len {
34940            if bytes[i] == b'%' && i + 1 < len {
34941                // Check for non-padded variants (%-X)
34942                if bytes[i + 1] == b'-' && i + 2 < len {
34943                    let replacement = match bytes[i + 2] {
34944                        b'd' => "d",
34945                        b'm' => "M",
34946                        b'H' => "H",
34947                        b'M' => "m",
34948                        b'S' => "s",
34949                        _ => {
34950                            result.push('%');
34951                            i += 1;
34952                            continue;
34953                        }
34954                    };
34955                    result.push_str(replacement);
34956                    i += 3;
34957                } else {
34958                    let replacement = match bytes[i + 1] {
34959                        b'Y' => "yyyy",
34960                        b'y' => "yy",
34961                        b'm' => "MM",
34962                        b'B' => "MMMM",
34963                        b'b' => "MMM",
34964                        b'd' => "dd",
34965                        b'j' => "DDD",
34966                        b'H' => "HH",
34967                        b'M' => "mm",
34968                        b'S' => "ss",
34969                        b'f' => "SSSSSS",
34970                        b'A' => "EEEE",
34971                        b'a' => "EEE",
34972                        _ => {
34973                            result.push('%');
34974                            i += 1;
34975                            continue;
34976                        }
34977                    };
34978                    result.push_str(replacement);
34979                    i += 2;
34980                }
34981            } else {
34982                result.push(bytes[i] as char);
34983                i += 1;
34984            }
34985        }
34986        result
34987    }
34988
34989    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34990    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34991    fn strftime_to_tsql_format(fmt: &str) -> String {
34992        let mut result = String::with_capacity(fmt.len() * 2);
34993        let bytes = fmt.as_bytes();
34994        let len = bytes.len();
34995        let mut i = 0;
34996        while i < len {
34997            if bytes[i] == b'%' && i + 1 < len {
34998                // Check for non-padded variants (%-X)
34999                if bytes[i + 1] == b'-' && i + 2 < len {
35000                    let replacement = match bytes[i + 2] {
35001                        b'd' => "d",
35002                        b'm' => "M",
35003                        b'H' => "H",
35004                        b'M' => "m",
35005                        b'S' => "s",
35006                        _ => {
35007                            result.push('%');
35008                            i += 1;
35009                            continue;
35010                        }
35011                    };
35012                    result.push_str(replacement);
35013                    i += 3;
35014                } else {
35015                    let replacement = match bytes[i + 1] {
35016                        b'Y' => "yyyy",
35017                        b'y' => "yy",
35018                        b'm' => "MM",
35019                        b'B' => "MMMM",
35020                        b'b' => "MMM",
35021                        b'd' => "dd",
35022                        b'j' => "DDD",
35023                        b'H' => "HH",
35024                        b'M' => "mm",
35025                        b'S' => "ss",
35026                        b'f' => "ffffff",
35027                        b'A' => "dddd",
35028                        b'a' => "ddd",
35029                        _ => {
35030                            result.push('%');
35031                            i += 1;
35032                            continue;
35033                        }
35034                    };
35035                    result.push_str(replacement);
35036                    i += 2;
35037                }
35038            } else {
35039                result.push(bytes[i] as char);
35040                i += 1;
35041            }
35042        }
35043        result
35044    }
35045
35046    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35047    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35048    fn decompose_json_path(path: &str) -> Vec<String> {
35049        let mut parts = Vec::new();
35050        // Strip leading $ and optional .
35051        let path = if path.starts_with("$.") {
35052            &path[2..]
35053        } else if path.starts_with('$') {
35054            &path[1..]
35055        } else {
35056            path
35057        };
35058        if path.is_empty() {
35059            return parts;
35060        }
35061        let mut current = String::new();
35062        let chars: Vec<char> = path.chars().collect();
35063        let mut i = 0;
35064        while i < chars.len() {
35065            match chars[i] {
35066                '.' => {
35067                    if !current.is_empty() {
35068                        parts.push(current.clone());
35069                        current.clear();
35070                    }
35071                    i += 1;
35072                }
35073                '[' => {
35074                    if !current.is_empty() {
35075                        parts.push(current.clone());
35076                        current.clear();
35077                    }
35078                    i += 1;
35079                    // Read the content inside brackets
35080                    let mut bracket_content = String::new();
35081                    while i < chars.len() && chars[i] != ']' {
35082                        // Skip quotes inside brackets
35083                        if chars[i] == '"' || chars[i] == '\'' {
35084                            let quote = chars[i];
35085                            i += 1;
35086                            while i < chars.len() && chars[i] != quote {
35087                                bracket_content.push(chars[i]);
35088                                i += 1;
35089                            }
35090                            if i < chars.len() {
35091                                i += 1;
35092                            } // skip closing quote
35093                        } else {
35094                            bracket_content.push(chars[i]);
35095                            i += 1;
35096                        }
35097                    }
35098                    if i < chars.len() {
35099                        i += 1;
35100                    } // skip ]
35101                      // Skip wildcard [*] - don't add as a part
35102                    if bracket_content != "*" {
35103                        parts.push(bracket_content);
35104                    }
35105                }
35106                _ => {
35107                    current.push(chars[i]);
35108                    i += 1;
35109                }
35110            }
35111        }
35112        if !current.is_empty() {
35113            parts.push(current);
35114        }
35115        parts
35116    }
35117
35118    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35119    fn strftime_to_postgres_format(fmt: &str) -> String {
35120        let mut result = String::with_capacity(fmt.len() * 2);
35121        let bytes = fmt.as_bytes();
35122        let len = bytes.len();
35123        let mut i = 0;
35124        while i < len {
35125            if bytes[i] == b'%' && i + 1 < len {
35126                // Check for non-padded variants (%-X)
35127                if bytes[i + 1] == b'-' && i + 2 < len {
35128                    let replacement = match bytes[i + 2] {
35129                        b'd' => "FMDD",
35130                        b'm' => "FMMM",
35131                        b'H' => "FMHH24",
35132                        b'M' => "FMMI",
35133                        b'S' => "FMSS",
35134                        _ => {
35135                            result.push('%');
35136                            i += 1;
35137                            continue;
35138                        }
35139                    };
35140                    result.push_str(replacement);
35141                    i += 3;
35142                } else {
35143                    let replacement = match bytes[i + 1] {
35144                        b'Y' => "YYYY",
35145                        b'y' => "YY",
35146                        b'm' => "MM",
35147                        b'B' => "Month",
35148                        b'b' => "Mon",
35149                        b'd' => "DD",
35150                        b'j' => "DDD",
35151                        b'H' => "HH24",
35152                        b'M' => "MI",
35153                        b'S' => "SS",
35154                        b'f' => "US",
35155                        b'A' => "Day",
35156                        b'a' => "Dy",
35157                        _ => {
35158                            result.push('%');
35159                            i += 1;
35160                            continue;
35161                        }
35162                    };
35163                    result.push_str(replacement);
35164                    i += 2;
35165                }
35166            } else {
35167                result.push(bytes[i] as char);
35168                i += 1;
35169            }
35170        }
35171        result
35172    }
35173
35174    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35175    fn strftime_to_snowflake_format(fmt: &str) -> String {
35176        let mut result = String::with_capacity(fmt.len() * 2);
35177        let bytes = fmt.as_bytes();
35178        let len = bytes.len();
35179        let mut i = 0;
35180        while i < len {
35181            if bytes[i] == b'%' && i + 1 < len {
35182                // Check for non-padded variants (%-X)
35183                if bytes[i + 1] == b'-' && i + 2 < len {
35184                    let replacement = match bytes[i + 2] {
35185                        b'd' => "dd",
35186                        b'm' => "mm",
35187                        _ => {
35188                            result.push('%');
35189                            i += 1;
35190                            continue;
35191                        }
35192                    };
35193                    result.push_str(replacement);
35194                    i += 3;
35195                } else {
35196                    let replacement = match bytes[i + 1] {
35197                        b'Y' => "yyyy",
35198                        b'y' => "yy",
35199                        b'm' => "mm",
35200                        b'd' => "DD",
35201                        b'H' => "hh24",
35202                        b'M' => "mi",
35203                        b'S' => "ss",
35204                        b'f' => "ff",
35205                        _ => {
35206                            result.push('%');
35207                            i += 1;
35208                            continue;
35209                        }
35210                    };
35211                    result.push_str(replacement);
35212                    i += 2;
35213                }
35214            } else {
35215                result.push(bytes[i] as char);
35216                i += 1;
35217            }
35218        }
35219        result
35220    }
35221
35222    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35223        // STR_TO_MAP(this, pair_delim, key_value_delim)
35224        self.write_keyword("STR_TO_MAP");
35225        self.write("(");
35226        self.generate_expression(&e.this)?;
35227        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35228        let needs_defaults = matches!(
35229            self.config.dialect,
35230            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35231        );
35232        if let Some(pair_delim) = &e.pair_delim {
35233            self.write(", ");
35234            self.generate_expression(pair_delim)?;
35235        } else if needs_defaults {
35236            self.write(", ','");
35237        }
35238        if let Some(key_value_delim) = &e.key_value_delim {
35239            self.write(", ");
35240            self.generate_expression(key_value_delim)?;
35241        } else if needs_defaults {
35242            self.write(", ':'");
35243        }
35244        self.write(")");
35245        Ok(())
35246    }
35247
35248    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35249        // Detect format style: strftime (starts with %) vs Snowflake/Java
35250        let is_strftime = e.format.contains('%');
35251        // Helper: get strftime format from whatever style is stored
35252        let to_strftime = |f: &str| -> String {
35253            if is_strftime {
35254                f.to_string()
35255            } else {
35256                Self::snowflake_format_to_strftime(f)
35257            }
35258        };
35259        // Helper: get Java format
35260        let to_java = |f: &str| -> String {
35261            if is_strftime {
35262                Self::strftime_to_java_format(f)
35263            } else {
35264                Self::snowflake_format_to_spark(f)
35265            }
35266        };
35267        // Helper: get PG format
35268        let to_pg = |f: &str| -> String {
35269            if is_strftime {
35270                Self::strftime_to_postgres_format(f)
35271            } else {
35272                Self::convert_strptime_to_postgres_format(f)
35273            }
35274        };
35275
35276        match self.config.dialect {
35277            Some(DialectType::Exasol) => {
35278                self.write_keyword("TO_DATE");
35279                self.write("(");
35280                self.generate_expression(&e.this)?;
35281                self.write(", '");
35282                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35283                self.write("'");
35284                self.write(")");
35285            }
35286            Some(DialectType::BigQuery) => {
35287                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35288                let fmt = to_strftime(&e.format);
35289                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35290                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35291                self.write_keyword("PARSE_TIMESTAMP");
35292                self.write("('");
35293                self.write(&fmt);
35294                self.write("', ");
35295                self.generate_expression(&e.this)?;
35296                self.write(")");
35297            }
35298            Some(DialectType::Hive) => {
35299                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35300                // Check both the raw format and the converted format (in case it's already Java)
35301                let java_fmt = to_java(&e.format);
35302                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35303                    || java_fmt == "yyyy-MM-dd"
35304                    || e.format == "yyyy-MM-dd HH:mm:ss"
35305                    || e.format == "yyyy-MM-dd"
35306                {
35307                    self.write_keyword("CAST");
35308                    self.write("(");
35309                    self.generate_expression(&e.this)?;
35310                    self.write(" ");
35311                    self.write_keyword("AS TIMESTAMP");
35312                    self.write(")");
35313                } else {
35314                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35315                    self.write_keyword("CAST");
35316                    self.write("(");
35317                    self.write_keyword("FROM_UNIXTIME");
35318                    self.write("(");
35319                    self.write_keyword("UNIX_TIMESTAMP");
35320                    self.write("(");
35321                    self.generate_expression(&e.this)?;
35322                    self.write(", '");
35323                    self.write(&java_fmt);
35324                    self.write("')");
35325                    self.write(") ");
35326                    self.write_keyword("AS TIMESTAMP");
35327                    self.write(")");
35328                }
35329            }
35330            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35331                // Spark: TO_TIMESTAMP(value, java_format)
35332                let java_fmt = to_java(&e.format);
35333                self.write_keyword("TO_TIMESTAMP");
35334                self.write("(");
35335                self.generate_expression(&e.this)?;
35336                self.write(", '");
35337                self.write(&java_fmt);
35338                self.write("')");
35339            }
35340            Some(DialectType::MySQL) => {
35341                // MySQL: STR_TO_DATE(value, format)
35342                let mut fmt = to_strftime(&e.format);
35343                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35344                fmt = fmt.replace("%-d", "%e");
35345                fmt = fmt.replace("%-m", "%c");
35346                fmt = fmt.replace("%H:%M:%S", "%T");
35347                self.write_keyword("STR_TO_DATE");
35348                self.write("(");
35349                self.generate_expression(&e.this)?;
35350                self.write(", '");
35351                self.write(&fmt);
35352                self.write("')");
35353            }
35354            Some(DialectType::Drill) => {
35355                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35356                let java_fmt = to_java(&e.format);
35357                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35358                let java_fmt = java_fmt.replace('T', "''T''");
35359                self.write_keyword("TO_TIMESTAMP");
35360                self.write("(");
35361                self.generate_expression(&e.this)?;
35362                self.write(", '");
35363                self.write(&java_fmt);
35364                self.write("')");
35365            }
35366            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35367                // Presto: DATE_PARSE(value, strftime_format)
35368                let mut fmt = to_strftime(&e.format);
35369                // Presto uses %e for non-padded day, %T for %H:%M:%S
35370                fmt = fmt.replace("%-d", "%e");
35371                fmt = fmt.replace("%-m", "%c");
35372                fmt = fmt.replace("%H:%M:%S", "%T");
35373                self.write_keyword("DATE_PARSE");
35374                self.write("(");
35375                self.generate_expression(&e.this)?;
35376                self.write(", '");
35377                self.write(&fmt);
35378                self.write("')");
35379            }
35380            Some(DialectType::DuckDB) => {
35381                // DuckDB: STRPTIME(value, strftime_format)
35382                let fmt = to_strftime(&e.format);
35383                self.write_keyword("STRPTIME");
35384                self.write("(");
35385                self.generate_expression(&e.this)?;
35386                self.write(", '");
35387                self.write(&fmt);
35388                self.write("')");
35389            }
35390            Some(DialectType::PostgreSQL)
35391            | Some(DialectType::Redshift)
35392            | Some(DialectType::Materialize) => {
35393                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35394                let pg_fmt = to_pg(&e.format);
35395                self.write_keyword("TO_TIMESTAMP");
35396                self.write("(");
35397                self.generate_expression(&e.this)?;
35398                self.write(", '");
35399                self.write(&pg_fmt);
35400                self.write("')");
35401            }
35402            Some(DialectType::Oracle) => {
35403                // Oracle: TO_TIMESTAMP(value, pg_format)
35404                let pg_fmt = to_pg(&e.format);
35405                self.write_keyword("TO_TIMESTAMP");
35406                self.write("(");
35407                self.generate_expression(&e.this)?;
35408                self.write(", '");
35409                self.write(&pg_fmt);
35410                self.write("')");
35411            }
35412            Some(DialectType::Snowflake) => {
35413                // Snowflake: TO_TIMESTAMP(value, format) - native format
35414                self.write_keyword("TO_TIMESTAMP");
35415                self.write("(");
35416                self.generate_expression(&e.this)?;
35417                self.write(", '");
35418                self.write(&e.format);
35419                self.write("')");
35420            }
35421            _ => {
35422                // Default: STR_TO_TIME(this, format)
35423                self.write_keyword("STR_TO_TIME");
35424                self.write("(");
35425                self.generate_expression(&e.this)?;
35426                self.write(", '");
35427                self.write(&e.format);
35428                self.write("'");
35429                self.write(")");
35430            }
35431        }
35432        Ok(())
35433    }
35434
35435    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35436    fn snowflake_format_to_strftime(format: &str) -> String {
35437        let mut result = String::new();
35438        let chars: Vec<char> = format.chars().collect();
35439        let mut i = 0;
35440        while i < chars.len() {
35441            let remaining = &format[i..];
35442            if remaining.starts_with("yyyy") {
35443                result.push_str("%Y");
35444                i += 4;
35445            } else if remaining.starts_with("yy") {
35446                result.push_str("%y");
35447                i += 2;
35448            } else if remaining.starts_with("mmmm") {
35449                result.push_str("%B"); // full month name
35450                i += 4;
35451            } else if remaining.starts_with("mon") {
35452                result.push_str("%b"); // abbreviated month
35453                i += 3;
35454            } else if remaining.starts_with("mm") {
35455                result.push_str("%m");
35456                i += 2;
35457            } else if remaining.starts_with("DD") {
35458                result.push_str("%d");
35459                i += 2;
35460            } else if remaining.starts_with("dy") {
35461                result.push_str("%a"); // abbreviated day name
35462                i += 2;
35463            } else if remaining.starts_with("hh24") {
35464                result.push_str("%H");
35465                i += 4;
35466            } else if remaining.starts_with("hh12") {
35467                result.push_str("%I");
35468                i += 4;
35469            } else if remaining.starts_with("hh") {
35470                result.push_str("%H");
35471                i += 2;
35472            } else if remaining.starts_with("mi") {
35473                result.push_str("%M");
35474                i += 2;
35475            } else if remaining.starts_with("ss") {
35476                result.push_str("%S");
35477                i += 2;
35478            } else if remaining.starts_with("ff") {
35479                // Fractional seconds
35480                result.push_str("%f");
35481                i += 2;
35482                // Skip digits after ff (ff3, ff6, ff9)
35483                while i < chars.len() && chars[i].is_ascii_digit() {
35484                    i += 1;
35485                }
35486            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35487                result.push_str("%p");
35488                i += 2;
35489            } else if remaining.starts_with("tz") {
35490                result.push_str("%Z");
35491                i += 2;
35492            } else {
35493                result.push(chars[i]);
35494                i += 1;
35495            }
35496        }
35497        result
35498    }
35499
35500    /// Convert Snowflake normalized format to Spark format (Java-style)
35501    fn snowflake_format_to_spark(format: &str) -> String {
35502        let mut result = String::new();
35503        let chars: Vec<char> = format.chars().collect();
35504        let mut i = 0;
35505        while i < chars.len() {
35506            let remaining = &format[i..];
35507            if remaining.starts_with("yyyy") {
35508                result.push_str("yyyy");
35509                i += 4;
35510            } else if remaining.starts_with("yy") {
35511                result.push_str("yy");
35512                i += 2;
35513            } else if remaining.starts_with("mmmm") {
35514                result.push_str("MMMM"); // full month name
35515                i += 4;
35516            } else if remaining.starts_with("mon") {
35517                result.push_str("MMM"); // abbreviated month
35518                i += 3;
35519            } else if remaining.starts_with("mm") {
35520                result.push_str("MM");
35521                i += 2;
35522            } else if remaining.starts_with("DD") {
35523                result.push_str("dd");
35524                i += 2;
35525            } else if remaining.starts_with("dy") {
35526                result.push_str("EEE"); // abbreviated day name
35527                i += 2;
35528            } else if remaining.starts_with("hh24") {
35529                result.push_str("HH");
35530                i += 4;
35531            } else if remaining.starts_with("hh12") {
35532                result.push_str("hh");
35533                i += 4;
35534            } else if remaining.starts_with("hh") {
35535                result.push_str("HH");
35536                i += 2;
35537            } else if remaining.starts_with("mi") {
35538                result.push_str("mm");
35539                i += 2;
35540            } else if remaining.starts_with("ss") {
35541                result.push_str("ss");
35542                i += 2;
35543            } else if remaining.starts_with("ff") {
35544                result.push_str("SSS"); // milliseconds
35545                i += 2;
35546                // Skip digits after ff
35547                while i < chars.len() && chars[i].is_ascii_digit() {
35548                    i += 1;
35549                }
35550            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
35551                result.push_str("a");
35552                i += 2;
35553            } else if remaining.starts_with("tz") {
35554                result.push_str("z");
35555                i += 2;
35556            } else {
35557                result.push(chars[i]);
35558                i += 1;
35559            }
35560        }
35561        result
35562    }
35563
35564    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
35565        match self.config.dialect {
35566            Some(DialectType::DuckDB) => {
35567                // DuckDB: EPOCH(STRPTIME(value, format))
35568                self.write_keyword("EPOCH");
35569                self.write("(");
35570                self.write_keyword("STRPTIME");
35571                self.write("(");
35572                if let Some(this) = &e.this {
35573                    self.generate_expression(this)?;
35574                }
35575                if let Some(format) = &e.format {
35576                    self.write(", '");
35577                    self.write(format);
35578                    self.write("'");
35579                }
35580                self.write("))");
35581            }
35582            Some(DialectType::Hive) => {
35583                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
35584                self.write_keyword("UNIX_TIMESTAMP");
35585                self.write("(");
35586                if let Some(this) = &e.this {
35587                    self.generate_expression(this)?;
35588                }
35589                if let Some(format) = &e.format {
35590                    let java_fmt = Self::strftime_to_java_format(format);
35591                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
35592                        self.write(", '");
35593                        self.write(&java_fmt);
35594                        self.write("'");
35595                    }
35596                }
35597                self.write(")");
35598            }
35599            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35600                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35601                self.write_keyword("UNIX_TIMESTAMP");
35602                self.write("(");
35603                if let Some(this) = &e.this {
35604                    self.generate_expression(this)?;
35605                }
35606                if let Some(format) = &e.format {
35607                    self.write(", '");
35608                    self.write(format);
35609                    self.write("'");
35610                }
35611                self.write(")");
35612            }
35613            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35614                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35615                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35616                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35617                let java_fmt = Self::strftime_to_java_format(c_fmt);
35618                self.write_keyword("TO_UNIXTIME");
35619                self.write("(");
35620                self.write_keyword("COALESCE");
35621                self.write("(");
35622                self.write_keyword("TRY");
35623                self.write("(");
35624                self.write_keyword("DATE_PARSE");
35625                self.write("(");
35626                self.write_keyword("CAST");
35627                self.write("(");
35628                if let Some(this) = &e.this {
35629                    self.generate_expression(this)?;
35630                }
35631                self.write(" ");
35632                self.write_keyword("AS VARCHAR");
35633                self.write("), '");
35634                self.write(c_fmt);
35635                self.write("')), ");
35636                self.write_keyword("PARSE_DATETIME");
35637                self.write("(");
35638                self.write_keyword("DATE_FORMAT");
35639                self.write("(");
35640                self.write_keyword("CAST");
35641                self.write("(");
35642                if let Some(this) = &e.this {
35643                    self.generate_expression(this)?;
35644                }
35645                self.write(" ");
35646                self.write_keyword("AS TIMESTAMP");
35647                self.write("), '");
35648                self.write(c_fmt);
35649                self.write("'), '");
35650                self.write(&java_fmt);
35651                self.write("')))");
35652            }
35653            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35654                // Spark: UNIX_TIMESTAMP(value, java_format)
35655                self.write_keyword("UNIX_TIMESTAMP");
35656                self.write("(");
35657                if let Some(this) = &e.this {
35658                    self.generate_expression(this)?;
35659                }
35660                if let Some(format) = &e.format {
35661                    let java_fmt = Self::strftime_to_java_format(format);
35662                    self.write(", '");
35663                    self.write(&java_fmt);
35664                    self.write("'");
35665                }
35666                self.write(")");
35667            }
35668            _ => {
35669                // Default: STR_TO_UNIX(this, format)
35670                self.write_keyword("STR_TO_UNIX");
35671                self.write("(");
35672                if let Some(this) = &e.this {
35673                    self.generate_expression(this)?;
35674                }
35675                if let Some(format) = &e.format {
35676                    self.write(", '");
35677                    self.write(format);
35678                    self.write("'");
35679                }
35680                self.write(")");
35681            }
35682        }
35683        Ok(())
35684    }
35685
35686    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35687        // STRING_TO_ARRAY(this, delimiter, null_string)
35688        self.write_keyword("STRING_TO_ARRAY");
35689        self.write("(");
35690        self.generate_expression(&e.this)?;
35691        if let Some(expression) = &e.expression {
35692            self.write(", ");
35693            self.generate_expression(expression)?;
35694        }
35695        if let Some(null_val) = &e.null {
35696            self.write(", ");
35697            self.generate_expression(null_val)?;
35698        }
35699        self.write(")");
35700        Ok(())
35701    }
35702
35703    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35704        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35705            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35706            self.write_keyword("OBJECT_CONSTRUCT");
35707            self.write("(");
35708            for (i, (name, expr)) in e.fields.iter().enumerate() {
35709                if i > 0 {
35710                    self.write(", ");
35711                }
35712                if let Some(name) = name {
35713                    self.write("'");
35714                    self.write(name);
35715                    self.write("'");
35716                    self.write(", ");
35717                } else {
35718                    self.write("'_");
35719                    self.write(&i.to_string());
35720                    self.write("'");
35721                    self.write(", ");
35722                }
35723                self.generate_expression(expr)?;
35724            }
35725            self.write(")");
35726        } else if self.config.struct_curly_brace_notation {
35727            // DuckDB-style: {'key': value, ...}
35728            self.write("{");
35729            for (i, (name, expr)) in e.fields.iter().enumerate() {
35730                if i > 0 {
35731                    self.write(", ");
35732                }
35733                if let Some(name) = name {
35734                    // Quote the key as a string literal
35735                    self.write("'");
35736                    self.write(name);
35737                    self.write("'");
35738                    self.write(": ");
35739                } else {
35740                    // Unnamed field: use positional key
35741                    self.write("'_");
35742                    self.write(&i.to_string());
35743                    self.write("'");
35744                    self.write(": ");
35745                }
35746                self.generate_expression(expr)?;
35747            }
35748            self.write("}");
35749        } else {
35750            // Standard SQL struct notation
35751            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35752            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35753            let value_as_name = matches!(
35754                self.config.dialect,
35755                Some(DialectType::BigQuery)
35756                    | Some(DialectType::Spark)
35757                    | Some(DialectType::Databricks)
35758                    | Some(DialectType::Hive)
35759            );
35760            self.write_keyword("STRUCT");
35761            self.write("(");
35762            for (i, (name, expr)) in e.fields.iter().enumerate() {
35763                if i > 0 {
35764                    self.write(", ");
35765                }
35766                if let Some(name) = name {
35767                    if value_as_name {
35768                        // STRUCT(value AS name)
35769                        self.generate_expression(expr)?;
35770                        self.write_space();
35771                        self.write_keyword("AS");
35772                        self.write_space();
35773                        // Quote name if it contains spaces or special chars
35774                        let needs_quoting = name.contains(' ') || name.contains('-');
35775                        if needs_quoting {
35776                            if matches!(
35777                                self.config.dialect,
35778                                Some(DialectType::Spark)
35779                                    | Some(DialectType::Databricks)
35780                                    | Some(DialectType::Hive)
35781                            ) {
35782                                self.write("`");
35783                                self.write(name);
35784                                self.write("`");
35785                            } else {
35786                                self.write(name);
35787                            }
35788                        } else {
35789                            self.write(name);
35790                        }
35791                    } else {
35792                        // STRUCT(name AS value)
35793                        self.write(name);
35794                        self.write_space();
35795                        self.write_keyword("AS");
35796                        self.write_space();
35797                        self.generate_expression(expr)?;
35798                    }
35799                } else {
35800                    self.generate_expression(expr)?;
35801                }
35802            }
35803            self.write(")");
35804        }
35805        Ok(())
35806    }
35807
35808    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35809        // STUFF(this, start, length, expression)
35810        self.write_keyword("STUFF");
35811        self.write("(");
35812        self.generate_expression(&e.this)?;
35813        if let Some(start) = &e.start {
35814            self.write(", ");
35815            self.generate_expression(start)?;
35816        }
35817        if let Some(length) = e.length {
35818            self.write(", ");
35819            self.write(&length.to_string());
35820        }
35821        self.write(", ");
35822        self.generate_expression(&e.expression)?;
35823        self.write(")");
35824        Ok(())
35825    }
35826
35827    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35828        // SUBSTRING_INDEX(this, delimiter, count)
35829        self.write_keyword("SUBSTRING_INDEX");
35830        self.write("(");
35831        self.generate_expression(&e.this)?;
35832        if let Some(delimiter) = &e.delimiter {
35833            self.write(", ");
35834            self.generate_expression(delimiter)?;
35835        }
35836        if let Some(count) = &e.count {
35837            self.write(", ");
35838            self.generate_expression(count)?;
35839        }
35840        self.write(")");
35841        Ok(())
35842    }
35843
35844    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35845        // SUMMARIZE [TABLE] this
35846        self.write_keyword("SUMMARIZE");
35847        if e.table.is_some() {
35848            self.write_space();
35849            self.write_keyword("TABLE");
35850        }
35851        self.write_space();
35852        self.generate_expression(&e.this)?;
35853        Ok(())
35854    }
35855
35856    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35857        // SYSTIMESTAMP
35858        self.write_keyword("SYSTIMESTAMP");
35859        Ok(())
35860    }
35861
35862    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35863        // alias (columns...)
35864        if let Some(this) = &e.this {
35865            self.generate_expression(this)?;
35866        }
35867        if !e.columns.is_empty() {
35868            self.write("(");
35869            for (i, col) in e.columns.iter().enumerate() {
35870                if i > 0 {
35871                    self.write(", ");
35872                }
35873                self.generate_expression(col)?;
35874            }
35875            self.write(")");
35876        }
35877        Ok(())
35878    }
35879
35880    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35881        // TABLE(this) [AS alias]
35882        self.write_keyword("TABLE");
35883        self.write("(");
35884        self.generate_expression(&e.this)?;
35885        self.write(")");
35886        if let Some(alias) = &e.alias {
35887            self.write_space();
35888            self.write_keyword("AS");
35889            self.write_space();
35890            self.write(alias);
35891        }
35892        Ok(())
35893    }
35894
35895    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35896        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35897        self.write_keyword("ROWS FROM");
35898        self.write(" (");
35899        for (i, expr) in e.expressions.iter().enumerate() {
35900            if i > 0 {
35901                self.write(", ");
35902            }
35903            // Each expression is either:
35904            // - A plain function (no alias)
35905            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35906            match expr {
35907                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35908                    // First element is the function, second is the TableAlias
35909                    self.generate_expression(&tuple.expressions[0])?;
35910                    self.write_space();
35911                    self.write_keyword("AS");
35912                    self.write_space();
35913                    self.generate_expression(&tuple.expressions[1])?;
35914                }
35915                _ => {
35916                    self.generate_expression(expr)?;
35917                }
35918            }
35919        }
35920        self.write(")");
35921        if e.ordinality {
35922            self.write_space();
35923            self.write_keyword("WITH ORDINALITY");
35924        }
35925        if let Some(alias) = &e.alias {
35926            self.write_space();
35927            self.write_keyword("AS");
35928            self.write_space();
35929            self.generate_expression(alias)?;
35930        }
35931        Ok(())
35932    }
35933
35934    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35935        use crate::dialects::DialectType;
35936
35937        // New wrapper pattern: expression + Sample struct
35938        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35939            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35940            if self.config.alias_post_tablesample {
35941                // Handle Subquery with alias and Alias wrapper
35942                if let Expression::Subquery(ref s) = **this {
35943                    if let Some(ref alias) = s.alias {
35944                        // Create a clone without alias for output
35945                        let mut subquery_no_alias = (**s).clone();
35946                        subquery_no_alias.alias = None;
35947                        subquery_no_alias.column_aliases = Vec::new();
35948                        self.generate_expression(&Expression::Subquery(Box::new(
35949                            subquery_no_alias,
35950                        )))?;
35951                        self.write_space();
35952                        self.write_keyword(self.config.tablesample_keywords);
35953                        self.generate_sample_body(sample)?;
35954                        if let Some(ref seed) = sample.seed {
35955                            self.write_space();
35956                            let use_seed = sample.use_seed_keyword
35957                                && !matches!(
35958                                    self.config.dialect,
35959                                    Some(crate::dialects::DialectType::Databricks)
35960                                        | Some(crate::dialects::DialectType::Spark)
35961                                );
35962                            if use_seed {
35963                                self.write_keyword("SEED");
35964                            } else {
35965                                self.write_keyword("REPEATABLE");
35966                            }
35967                            self.write(" (");
35968                            self.generate_expression(seed)?;
35969                            self.write(")");
35970                        }
35971                        self.write_space();
35972                        self.write_keyword("AS");
35973                        self.write_space();
35974                        self.generate_identifier(alias)?;
35975                        return Ok(());
35976                    }
35977                } else if let Expression::Alias(ref a) = **this {
35978                    // Output the base expression without alias
35979                    self.generate_expression(&a.this)?;
35980                    self.write_space();
35981                    self.write_keyword(self.config.tablesample_keywords);
35982                    self.generate_sample_body(sample)?;
35983                    if let Some(ref seed) = sample.seed {
35984                        self.write_space();
35985                        let use_seed = sample.use_seed_keyword
35986                            && !matches!(
35987                                self.config.dialect,
35988                                Some(crate::dialects::DialectType::Databricks)
35989                                    | Some(crate::dialects::DialectType::Spark)
35990                            );
35991                        if use_seed {
35992                            self.write_keyword("SEED");
35993                        } else {
35994                            self.write_keyword("REPEATABLE");
35995                        }
35996                        self.write(" (");
35997                        self.generate_expression(seed)?;
35998                        self.write(")");
35999                    }
36000                    // Output alias after TABLESAMPLE
36001                    self.write_space();
36002                    self.write_keyword("AS");
36003                    self.write_space();
36004                    self.generate_identifier(&a.alias)?;
36005                    return Ok(());
36006                }
36007            }
36008            // Default: generate wrapped expression first, then TABLESAMPLE
36009            self.generate_expression(this)?;
36010            self.write_space();
36011            self.write_keyword(self.config.tablesample_keywords);
36012            self.generate_sample_body(sample)?;
36013            // Seed for table-level sample
36014            if let Some(ref seed) = sample.seed {
36015                self.write_space();
36016                // Databricks uses REPEATABLE, not SEED
36017                let use_seed = sample.use_seed_keyword
36018                    && !matches!(
36019                        self.config.dialect,
36020                        Some(crate::dialects::DialectType::Databricks)
36021                            | Some(crate::dialects::DialectType::Spark)
36022                    );
36023                if use_seed {
36024                    self.write_keyword("SEED");
36025                } else {
36026                    self.write_keyword("REPEATABLE");
36027                }
36028                self.write(" (");
36029                self.generate_expression(seed)?;
36030                self.write(")");
36031            }
36032            return Ok(());
36033        }
36034
36035        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36036        self.write_keyword(self.config.tablesample_keywords);
36037        if let Some(method) = &e.method {
36038            self.write_space();
36039            self.write_keyword(method);
36040        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36041            // Snowflake defaults to BERNOULLI when no method is specified
36042            self.write_space();
36043            self.write_keyword("BERNOULLI");
36044        }
36045        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36046            self.write_space();
36047            self.write_keyword("BUCKET");
36048            self.write_space();
36049            self.generate_expression(numerator)?;
36050            self.write_space();
36051            self.write_keyword("OUT OF");
36052            self.write_space();
36053            self.generate_expression(denominator)?;
36054            if let Some(field) = &e.bucket_field {
36055                self.write_space();
36056                self.write_keyword("ON");
36057                self.write_space();
36058                self.generate_expression(field)?;
36059            }
36060        } else if !e.expressions.is_empty() {
36061            self.write(" (");
36062            for (i, expr) in e.expressions.iter().enumerate() {
36063                if i > 0 {
36064                    self.write(", ");
36065                }
36066                self.generate_expression(expr)?;
36067            }
36068            self.write(")");
36069        } else if let Some(percent) = &e.percent {
36070            self.write(" (");
36071            self.generate_expression(percent)?;
36072            self.write_space();
36073            self.write_keyword("PERCENT");
36074            self.write(")");
36075        }
36076        Ok(())
36077    }
36078
36079    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36080        // [prefix]this[postfix]
36081        if let Some(prefix) = &e.prefix {
36082            self.generate_expression(prefix)?;
36083        }
36084        if let Some(this) = &e.this {
36085            self.generate_expression(this)?;
36086        }
36087        if let Some(postfix) = &e.postfix {
36088            self.generate_expression(postfix)?;
36089        }
36090        Ok(())
36091    }
36092
36093    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36094        // TAG (expressions)
36095        self.write_keyword("TAG");
36096        self.write(" (");
36097        for (i, expr) in e.expressions.iter().enumerate() {
36098            if i > 0 {
36099                self.write(", ");
36100            }
36101            self.generate_expression(expr)?;
36102        }
36103        self.write(")");
36104        Ok(())
36105    }
36106
36107    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36108        // TEMPORARY or TEMP or [this] TEMPORARY
36109        if let Some(this) = &e.this {
36110            self.generate_expression(this)?;
36111            self.write_space();
36112        }
36113        self.write_keyword("TEMPORARY");
36114        Ok(())
36115    }
36116
36117    /// Generate a Time function expression
36118    /// For most dialects: TIME('value')
36119    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36120        // Standard: TIME(value)
36121        self.write_keyword("TIME");
36122        self.write("(");
36123        self.generate_expression(&e.this)?;
36124        self.write(")");
36125        Ok(())
36126    }
36127
36128    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36129        // TIME_ADD(this, expression, unit)
36130        self.write_keyword("TIME_ADD");
36131        self.write("(");
36132        self.generate_expression(&e.this)?;
36133        self.write(", ");
36134        self.generate_expression(&e.expression)?;
36135        if let Some(unit) = &e.unit {
36136            self.write(", ");
36137            self.write_keyword(unit);
36138        }
36139        self.write(")");
36140        Ok(())
36141    }
36142
36143    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36144        // TIME_DIFF(this, expression, unit)
36145        self.write_keyword("TIME_DIFF");
36146        self.write("(");
36147        self.generate_expression(&e.this)?;
36148        self.write(", ");
36149        self.generate_expression(&e.expression)?;
36150        if let Some(unit) = &e.unit {
36151            self.write(", ");
36152            self.write_keyword(unit);
36153        }
36154        self.write(")");
36155        Ok(())
36156    }
36157
36158    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36159        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36160        self.write_keyword("TIME_FROM_PARTS");
36161        self.write("(");
36162        let mut first = true;
36163        if let Some(hour) = &e.hour {
36164            self.generate_expression(hour)?;
36165            first = false;
36166        }
36167        if let Some(minute) = &e.min {
36168            if !first {
36169                self.write(", ");
36170            }
36171            self.generate_expression(minute)?;
36172            first = false;
36173        }
36174        if let Some(second) = &e.sec {
36175            if !first {
36176                self.write(", ");
36177            }
36178            self.generate_expression(second)?;
36179            first = false;
36180        }
36181        if let Some(ns) = &e.nano {
36182            if !first {
36183                self.write(", ");
36184            }
36185            self.generate_expression(ns)?;
36186        }
36187        self.write(")");
36188        Ok(())
36189    }
36190
36191    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36192        // TIME_SLICE(this, expression, unit)
36193        self.write_keyword("TIME_SLICE");
36194        self.write("(");
36195        self.generate_expression(&e.this)?;
36196        self.write(", ");
36197        self.generate_expression(&e.expression)?;
36198        self.write(", ");
36199        self.write_keyword(&e.unit);
36200        self.write(")");
36201        Ok(())
36202    }
36203
36204    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36205        // TIME_STR_TO_TIME(this)
36206        self.write_keyword("TIME_STR_TO_TIME");
36207        self.write("(");
36208        self.generate_expression(&e.this)?;
36209        self.write(")");
36210        Ok(())
36211    }
36212
36213    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36214        // TIME_SUB(this, expression, unit)
36215        self.write_keyword("TIME_SUB");
36216        self.write("(");
36217        self.generate_expression(&e.this)?;
36218        self.write(", ");
36219        self.generate_expression(&e.expression)?;
36220        if let Some(unit) = &e.unit {
36221            self.write(", ");
36222            self.write_keyword(unit);
36223        }
36224        self.write(")");
36225        Ok(())
36226    }
36227
36228    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36229        match self.config.dialect {
36230            Some(DialectType::Exasol) => {
36231                // Exasol uses TO_CHAR with Exasol-specific format
36232                self.write_keyword("TO_CHAR");
36233                self.write("(");
36234                self.generate_expression(&e.this)?;
36235                self.write(", '");
36236                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36237                self.write("'");
36238                self.write(")");
36239            }
36240            Some(DialectType::PostgreSQL)
36241            | Some(DialectType::Redshift)
36242            | Some(DialectType::Materialize) => {
36243                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36244                self.write_keyword("TO_CHAR");
36245                self.write("(");
36246                self.generate_expression(&e.this)?;
36247                self.write(", '");
36248                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36249                self.write("'");
36250                self.write(")");
36251            }
36252            Some(DialectType::Oracle) => {
36253                // Oracle uses TO_CHAR with PG-like format
36254                self.write_keyword("TO_CHAR");
36255                self.write("(");
36256                self.generate_expression(&e.this)?;
36257                self.write(", '");
36258                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36259                self.write("'");
36260                self.write(")");
36261            }
36262            Some(DialectType::Drill) => {
36263                // Drill: TO_CHAR with Java format
36264                self.write_keyword("TO_CHAR");
36265                self.write("(");
36266                self.generate_expression(&e.this)?;
36267                self.write(", '");
36268                self.write(&Self::strftime_to_java_format(&e.format));
36269                self.write("'");
36270                self.write(")");
36271            }
36272            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36273                // TSQL: FORMAT(value, format) with .NET-style format
36274                self.write_keyword("FORMAT");
36275                self.write("(");
36276                self.generate_expression(&e.this)?;
36277                self.write(", '");
36278                self.write(&Self::strftime_to_tsql_format(&e.format));
36279                self.write("'");
36280                self.write(")");
36281            }
36282            Some(DialectType::DuckDB) => {
36283                // DuckDB: STRFTIME(value, format) - keeps C format
36284                self.write_keyword("STRFTIME");
36285                self.write("(");
36286                self.generate_expression(&e.this)?;
36287                self.write(", '");
36288                self.write(&e.format);
36289                self.write("'");
36290                self.write(")");
36291            }
36292            Some(DialectType::BigQuery) => {
36293                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36294                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36295                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36296                self.write_keyword("FORMAT_DATE");
36297                self.write("('");
36298                self.write(&fmt);
36299                self.write("', ");
36300                self.generate_expression(&e.this)?;
36301                self.write(")");
36302            }
36303            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36304                // Hive/Spark: DATE_FORMAT(value, java_format)
36305                self.write_keyword("DATE_FORMAT");
36306                self.write("(");
36307                self.generate_expression(&e.this)?;
36308                self.write(", '");
36309                self.write(&Self::strftime_to_java_format(&e.format));
36310                self.write("'");
36311                self.write(")");
36312            }
36313            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36314                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36315                self.write_keyword("DATE_FORMAT");
36316                self.write("(");
36317                self.generate_expression(&e.this)?;
36318                self.write(", '");
36319                self.write(&e.format);
36320                self.write("'");
36321                self.write(")");
36322            }
36323            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36324                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36325                self.write_keyword("DATE_FORMAT");
36326                self.write("(");
36327                self.generate_expression(&e.this)?;
36328                self.write(", '");
36329                self.write(&e.format);
36330                self.write("'");
36331                self.write(")");
36332            }
36333            _ => {
36334                // Default: TIME_TO_STR(this, format)
36335                self.write_keyword("TIME_TO_STR");
36336                self.write("(");
36337                self.generate_expression(&e.this)?;
36338                self.write(", '");
36339                self.write(&e.format);
36340                self.write("'");
36341                self.write(")");
36342            }
36343        }
36344        Ok(())
36345    }
36346
36347    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36348        match self.config.dialect {
36349            Some(DialectType::DuckDB) => {
36350                // DuckDB: EPOCH(x)
36351                self.write_keyword("EPOCH");
36352                self.write("(");
36353                self.generate_expression(&e.this)?;
36354                self.write(")");
36355            }
36356            Some(DialectType::Hive)
36357            | Some(DialectType::Spark)
36358            | Some(DialectType::Databricks)
36359            | Some(DialectType::Doris)
36360            | Some(DialectType::StarRocks)
36361            | Some(DialectType::Drill) => {
36362                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36363                self.write_keyword("UNIX_TIMESTAMP");
36364                self.write("(");
36365                self.generate_expression(&e.this)?;
36366                self.write(")");
36367            }
36368            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36369                // Presto: TO_UNIXTIME(x)
36370                self.write_keyword("TO_UNIXTIME");
36371                self.write("(");
36372                self.generate_expression(&e.this)?;
36373                self.write(")");
36374            }
36375            _ => {
36376                // Default: TIME_TO_UNIX(x)
36377                self.write_keyword("TIME_TO_UNIX");
36378                self.write("(");
36379                self.generate_expression(&e.this)?;
36380                self.write(")");
36381            }
36382        }
36383        Ok(())
36384    }
36385
36386    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36387        match self.config.dialect {
36388            Some(DialectType::Hive) => {
36389                // Hive: TO_DATE(x)
36390                self.write_keyword("TO_DATE");
36391                self.write("(");
36392                self.generate_expression(&e.this)?;
36393                self.write(")");
36394            }
36395            _ => {
36396                // Default: TIME_STR_TO_DATE(x)
36397                self.write_keyword("TIME_STR_TO_DATE");
36398                self.write("(");
36399                self.generate_expression(&e.this)?;
36400                self.write(")");
36401            }
36402        }
36403        Ok(())
36404    }
36405
36406    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36407        // TIME_TRUNC(this, unit)
36408        self.write_keyword("TIME_TRUNC");
36409        self.write("(");
36410        self.generate_expression(&e.this)?;
36411        self.write(", ");
36412        self.write_keyword(&e.unit);
36413        self.write(")");
36414        Ok(())
36415    }
36416
36417    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36418        // Just output the unit name
36419        if let Some(unit) = &e.unit {
36420            self.write_keyword(unit);
36421        }
36422        Ok(())
36423    }
36424
36425    /// Generate a Timestamp function expression
36426    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36427    /// For other dialects: TIMESTAMP('value')
36428    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36429        use crate::dialects::DialectType;
36430        use crate::expressions::Literal;
36431
36432        match self.config.dialect {
36433            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36434            Some(DialectType::Exasol) => {
36435                self.write_keyword("TO_TIMESTAMP");
36436                self.write("(");
36437                // Extract the string value from the expression if it's a string literal
36438                if let Some(this) = &e.this {
36439                    match this.as_ref() {
36440                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36441                            let Literal::String(s) = lit.as_ref() else {
36442                                unreachable!()
36443                            };
36444                            self.write("'");
36445                            self.write(s);
36446                            self.write("'");
36447                        }
36448                        _ => {
36449                            self.generate_expression(this)?;
36450                        }
36451                    }
36452                }
36453                self.write(")");
36454            }
36455            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36456            _ => {
36457                self.write_keyword("TIMESTAMP");
36458                self.write("(");
36459                if let Some(this) = &e.this {
36460                    self.generate_expression(this)?;
36461                }
36462                if let Some(zone) = &e.zone {
36463                    self.write(", ");
36464                    self.generate_expression(zone)?;
36465                }
36466                self.write(")");
36467            }
36468        }
36469        Ok(())
36470    }
36471
36472    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
36473        // TIMESTAMP_ADD(this, expression, unit)
36474        self.write_keyword("TIMESTAMP_ADD");
36475        self.write("(");
36476        self.generate_expression(&e.this)?;
36477        self.write(", ");
36478        self.generate_expression(&e.expression)?;
36479        if let Some(unit) = &e.unit {
36480            self.write(", ");
36481            self.write_keyword(unit);
36482        }
36483        self.write(")");
36484        Ok(())
36485    }
36486
36487    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
36488        // TIMESTAMP_DIFF(this, expression, unit)
36489        self.write_keyword("TIMESTAMP_DIFF");
36490        self.write("(");
36491        self.generate_expression(&e.this)?;
36492        self.write(", ");
36493        self.generate_expression(&e.expression)?;
36494        if let Some(unit) = &e.unit {
36495            self.write(", ");
36496            self.write_keyword(unit);
36497        }
36498        self.write(")");
36499        Ok(())
36500    }
36501
36502    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
36503        // TIMESTAMP_FROM_PARTS(this, expression)
36504        self.write_keyword("TIMESTAMP_FROM_PARTS");
36505        self.write("(");
36506        if let Some(this) = &e.this {
36507            self.generate_expression(this)?;
36508        }
36509        if let Some(expression) = &e.expression {
36510            self.write(", ");
36511            self.generate_expression(expression)?;
36512        }
36513        if let Some(zone) = &e.zone {
36514            self.write(", ");
36515            self.generate_expression(zone)?;
36516        }
36517        if let Some(milli) = &e.milli {
36518            self.write(", ");
36519            self.generate_expression(milli)?;
36520        }
36521        self.write(")");
36522        Ok(())
36523    }
36524
36525    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
36526        // TIMESTAMP_SUB(this, INTERVAL expression unit)
36527        self.write_keyword("TIMESTAMP_SUB");
36528        self.write("(");
36529        self.generate_expression(&e.this)?;
36530        self.write(", ");
36531        self.write_keyword("INTERVAL");
36532        self.write_space();
36533        self.generate_expression(&e.expression)?;
36534        if let Some(unit) = &e.unit {
36535            self.write_space();
36536            self.write_keyword(unit);
36537        }
36538        self.write(")");
36539        Ok(())
36540    }
36541
36542    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
36543        // TIMESTAMP_TZ_FROM_PARTS(...)
36544        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
36545        self.write("(");
36546        if let Some(zone) = &e.zone {
36547            self.generate_expression(zone)?;
36548        }
36549        self.write(")");
36550        Ok(())
36551    }
36552
36553    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
36554        // TO_BINARY(this, [format])
36555        self.write_keyword("TO_BINARY");
36556        self.write("(");
36557        self.generate_expression(&e.this)?;
36558        if let Some(format) = &e.format {
36559            self.write(", '");
36560            self.write(format);
36561            self.write("'");
36562        }
36563        self.write(")");
36564        Ok(())
36565    }
36566
36567    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
36568        // TO_BOOLEAN(this)
36569        self.write_keyword("TO_BOOLEAN");
36570        self.write("(");
36571        self.generate_expression(&e.this)?;
36572        self.write(")");
36573        Ok(())
36574    }
36575
36576    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
36577        // TO_CHAR(this, [format], [nlsparam])
36578        self.write_keyword("TO_CHAR");
36579        self.write("(");
36580        self.generate_expression(&e.this)?;
36581        if let Some(format) = &e.format {
36582            self.write(", '");
36583            self.write(format);
36584            self.write("'");
36585        }
36586        if let Some(nlsparam) = &e.nlsparam {
36587            self.write(", ");
36588            self.generate_expression(nlsparam)?;
36589        }
36590        self.write(")");
36591        Ok(())
36592    }
36593
36594    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
36595        // TO_DECFLOAT(this, [format])
36596        self.write_keyword("TO_DECFLOAT");
36597        self.write("(");
36598        self.generate_expression(&e.this)?;
36599        if let Some(format) = &e.format {
36600            self.write(", '");
36601            self.write(format);
36602            self.write("'");
36603        }
36604        self.write(")");
36605        Ok(())
36606    }
36607
36608    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36609        // TO_DOUBLE(this, [format])
36610        self.write_keyword("TO_DOUBLE");
36611        self.write("(");
36612        self.generate_expression(&e.this)?;
36613        if let Some(format) = &e.format {
36614            self.write(", '");
36615            self.write(format);
36616            self.write("'");
36617        }
36618        self.write(")");
36619        Ok(())
36620    }
36621
36622    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36623        // TO_FILE(this, path)
36624        self.write_keyword("TO_FILE");
36625        self.write("(");
36626        self.generate_expression(&e.this)?;
36627        if let Some(path) = &e.path {
36628            self.write(", ");
36629            self.generate_expression(path)?;
36630        }
36631        self.write(")");
36632        Ok(())
36633    }
36634
36635    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36636        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36637        // If safe flag is set, output TRY_TO_NUMBER
36638        let is_safe = e.safe.is_some();
36639        if is_safe {
36640            self.write_keyword("TRY_TO_NUMBER");
36641        } else {
36642            self.write_keyword("TO_NUMBER");
36643        }
36644        self.write("(");
36645        self.generate_expression(&e.this)?;
36646        let precision_is_snowflake_default = e.precision.is_none()
36647            || matches!(
36648                e.precision.as_deref(),
36649                Some(Expression::Literal(lit))
36650                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
36651            );
36652        let is_snowflake_default_precision =
36653            matches!(self.config.dialect, Some(DialectType::Snowflake))
36654                && e.nlsparam.is_none()
36655                && e.scale.is_none()
36656                && matches!(
36657                    e.format.as_deref(),
36658                    Some(Expression::Literal(lit))
36659                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
36660                )
36661                && precision_is_snowflake_default;
36662
36663        if !is_snowflake_default_precision {
36664            if let Some(format) = &e.format {
36665                self.write(", ");
36666                self.generate_expression(format)?;
36667            }
36668            if let Some(nlsparam) = &e.nlsparam {
36669                self.write(", ");
36670                self.generate_expression(nlsparam)?;
36671            }
36672            if let Some(precision) = &e.precision {
36673                self.write(", ");
36674                self.generate_expression(precision)?;
36675            }
36676            if let Some(scale) = &e.scale {
36677                self.write(", ");
36678                self.generate_expression(scale)?;
36679            }
36680        }
36681        self.write(")");
36682        Ok(())
36683    }
36684
36685    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36686        // TO_TABLE this
36687        self.write_keyword("TO_TABLE");
36688        self.write_space();
36689        self.generate_expression(&e.this)?;
36690        Ok(())
36691    }
36692
36693    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36694        // Check mark to determine the format
36695        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36696            Expression::Identifier(id) => id.name.clone(),
36697            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36698                let Literal::String(s) = lit.as_ref() else {
36699                    unreachable!()
36700                };
36701                s.clone()
36702            }
36703            _ => String::new(),
36704        });
36705
36706        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36707        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36708        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36709            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36710        });
36711
36712        // For Presto/Trino: always use START TRANSACTION
36713        let use_start_transaction = matches!(
36714            self.config.dialect,
36715            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36716        );
36717        // For most dialects: strip TRANSACTION keyword
36718        let strip_transaction = matches!(
36719            self.config.dialect,
36720            Some(DialectType::Snowflake)
36721                | Some(DialectType::PostgreSQL)
36722                | Some(DialectType::Redshift)
36723                | Some(DialectType::MySQL)
36724                | Some(DialectType::Hive)
36725                | Some(DialectType::Spark)
36726                | Some(DialectType::Databricks)
36727                | Some(DialectType::DuckDB)
36728                | Some(DialectType::Oracle)
36729                | Some(DialectType::Doris)
36730                | Some(DialectType::StarRocks)
36731                | Some(DialectType::Materialize)
36732                | Some(DialectType::ClickHouse)
36733        );
36734
36735        if is_start || use_start_transaction {
36736            // START TRANSACTION [modes]
36737            self.write_keyword("START TRANSACTION");
36738            if let Some(modes) = &e.modes {
36739                self.write_space();
36740                self.generate_expression(modes)?;
36741            }
36742        } else {
36743            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36744            self.write_keyword("BEGIN");
36745
36746            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36747            let is_kind = e.this.as_ref().map_or(false, |t| {
36748                if let Expression::Identifier(id) = t.as_ref() {
36749                    id.name.eq_ignore_ascii_case("DEFERRED")
36750                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36751                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36752                } else {
36753                    false
36754                }
36755            });
36756
36757            // Output kind before TRANSACTION keyword
36758            if is_kind {
36759                if let Some(this) = &e.this {
36760                    self.write_space();
36761                    if let Expression::Identifier(id) = this.as_ref() {
36762                        self.write_keyword(&id.name);
36763                    }
36764                }
36765            }
36766
36767            // Output TRANSACTION keyword if it was present and target supports it
36768            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36769                self.write_space();
36770                self.write_keyword("TRANSACTION");
36771            }
36772
36773            // Output transaction name (not kind)
36774            if !is_kind {
36775                if let Some(this) = &e.this {
36776                    self.write_space();
36777                    self.generate_expression(this)?;
36778                }
36779            }
36780
36781            // Output WITH MARK 'description' for TSQL
36782            if has_with_mark {
36783                self.write_space();
36784                self.write_keyword("WITH MARK");
36785                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36786                    if let Literal::String(desc) = lit.as_ref() {
36787                        if !desc.is_empty() {
36788                            self.write_space();
36789                            self.write(&format!("'{}'", desc));
36790                        }
36791                    }
36792                }
36793            }
36794
36795            // Output modes (isolation levels, etc.)
36796            if let Some(modes) = &e.modes {
36797                self.write_space();
36798                self.generate_expression(modes)?;
36799            }
36800        }
36801        Ok(())
36802    }
36803
36804    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36805        // TRANSFORM(this, expression)
36806        self.write_keyword("TRANSFORM");
36807        self.write("(");
36808        self.generate_expression(&e.this)?;
36809        self.write(", ");
36810        self.generate_expression(&e.expression)?;
36811        self.write(")");
36812        Ok(())
36813    }
36814
36815    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36816        // TRANSFORM(expressions)
36817        self.write_keyword("TRANSFORM");
36818        self.write("(");
36819        if self.config.pretty && !e.expressions.is_empty() {
36820            self.indent_level += 1;
36821            for (i, expr) in e.expressions.iter().enumerate() {
36822                if i > 0 {
36823                    self.write(",");
36824                }
36825                self.write_newline();
36826                self.write_indent();
36827                self.generate_expression(expr)?;
36828            }
36829            self.indent_level -= 1;
36830            self.write_newline();
36831            self.write(")");
36832        } else {
36833            for (i, expr) in e.expressions.iter().enumerate() {
36834                if i > 0 {
36835                    self.write(", ");
36836                }
36837                self.generate_expression(expr)?;
36838            }
36839            self.write(")");
36840        }
36841        Ok(())
36842    }
36843
36844    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36845        use crate::dialects::DialectType;
36846        // TRANSIENT is Snowflake-specific; skip for other dialects
36847        if let Some(this) = &e.this {
36848            self.generate_expression(this)?;
36849            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36850                self.write_space();
36851            }
36852        }
36853        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36854            self.write_keyword("TRANSIENT");
36855        }
36856        Ok(())
36857    }
36858
36859    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36860        // TRANSLATE(this, from_, to)
36861        self.write_keyword("TRANSLATE");
36862        self.write("(");
36863        self.generate_expression(&e.this)?;
36864        if let Some(from) = &e.from_ {
36865            self.write(", ");
36866            self.generate_expression(from)?;
36867        }
36868        if let Some(to) = &e.to {
36869            self.write(", ");
36870            self.generate_expression(to)?;
36871        }
36872        self.write(")");
36873        Ok(())
36874    }
36875
36876    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36877        // TRANSLATE(this USING expression)
36878        self.write_keyword("TRANSLATE");
36879        self.write("(");
36880        self.generate_expression(&e.this)?;
36881        self.write_space();
36882        self.write_keyword("USING");
36883        self.write_space();
36884        self.generate_expression(&e.expression)?;
36885        if e.with_error.is_some() {
36886            self.write_space();
36887            self.write_keyword("WITH ERROR");
36888        }
36889        self.write(")");
36890        Ok(())
36891    }
36892
36893    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36894        // TRUNCATE TABLE table1, table2, ...
36895        self.write_keyword("TRUNCATE TABLE");
36896        self.write_space();
36897        for (i, expr) in e.expressions.iter().enumerate() {
36898            if i > 0 {
36899                self.write(", ");
36900            }
36901            self.generate_expression(expr)?;
36902        }
36903        Ok(())
36904    }
36905
36906    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36907        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36908        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36909        self.write("(");
36910        self.generate_expression(&e.this)?;
36911        if let Some(alphabet) = &e.alphabet {
36912            self.write(", ");
36913            self.generate_expression(alphabet)?;
36914        }
36915        self.write(")");
36916        Ok(())
36917    }
36918
36919    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36920        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36921        self.write_keyword("TRY_BASE64_DECODE_STRING");
36922        self.write("(");
36923        self.generate_expression(&e.this)?;
36924        if let Some(alphabet) = &e.alphabet {
36925            self.write(", ");
36926            self.generate_expression(alphabet)?;
36927        }
36928        self.write(")");
36929        Ok(())
36930    }
36931
36932    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36933        // TRY_TO_DECFLOAT(this, [format])
36934        self.write_keyword("TRY_TO_DECFLOAT");
36935        self.write("(");
36936        self.generate_expression(&e.this)?;
36937        if let Some(format) = &e.format {
36938            self.write(", '");
36939            self.write(format);
36940            self.write("'");
36941        }
36942        self.write(")");
36943        Ok(())
36944    }
36945
36946    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36947        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36948        self.write_keyword("TS_OR_DS_ADD");
36949        self.write("(");
36950        self.generate_expression(&e.this)?;
36951        self.write(", ");
36952        self.generate_expression(&e.expression)?;
36953        if let Some(unit) = &e.unit {
36954            self.write(", ");
36955            self.write_keyword(unit);
36956        }
36957        if let Some(return_type) = &e.return_type {
36958            self.write(", ");
36959            self.generate_expression(return_type)?;
36960        }
36961        self.write(")");
36962        Ok(())
36963    }
36964
36965    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36966        // TS_OR_DS_DIFF(this, expression, [unit])
36967        self.write_keyword("TS_OR_DS_DIFF");
36968        self.write("(");
36969        self.generate_expression(&e.this)?;
36970        self.write(", ");
36971        self.generate_expression(&e.expression)?;
36972        if let Some(unit) = &e.unit {
36973            self.write(", ");
36974            self.write_keyword(unit);
36975        }
36976        self.write(")");
36977        Ok(())
36978    }
36979
36980    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36981        let default_time_format = "%Y-%m-%d %H:%M:%S";
36982        let default_date_format = "%Y-%m-%d";
36983        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36984            f != default_time_format && f != default_date_format
36985        });
36986
36987        if has_non_default_format {
36988            // With non-default format: dialect-specific handling
36989            let fmt = e.format.as_ref().unwrap();
36990            match self.config.dialect {
36991                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36992                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36993                    // STR_TO_DATE is the MySQL-native form of StrToTime
36994                    let str_to_time = crate::expressions::StrToTime {
36995                        this: Box::new((*e.this).clone()),
36996                        format: fmt.clone(),
36997                        zone: None,
36998                        safe: None,
36999                        target_type: None,
37000                    };
37001                    self.generate_str_to_time(&str_to_time)?;
37002                }
37003                Some(DialectType::Hive)
37004                | Some(DialectType::Spark)
37005                | Some(DialectType::Databricks) => {
37006                    // Hive/Spark: TO_DATE(x, java_fmt)
37007                    self.write_keyword("TO_DATE");
37008                    self.write("(");
37009                    self.generate_expression(&e.this)?;
37010                    self.write(", '");
37011                    self.write(&Self::strftime_to_java_format(fmt));
37012                    self.write("')");
37013                }
37014                Some(DialectType::Snowflake) => {
37015                    // Snowflake: TO_DATE(x, snowflake_fmt)
37016                    self.write_keyword("TO_DATE");
37017                    self.write("(");
37018                    self.generate_expression(&e.this)?;
37019                    self.write(", '");
37020                    self.write(&Self::strftime_to_snowflake_format(fmt));
37021                    self.write("')");
37022                }
37023                Some(DialectType::Doris) => {
37024                    // Doris: TO_DATE(x) - ignores format
37025                    self.write_keyword("TO_DATE");
37026                    self.write("(");
37027                    self.generate_expression(&e.this)?;
37028                    self.write(")");
37029                }
37030                _ => {
37031                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37032                    self.write_keyword("CAST");
37033                    self.write("(");
37034                    let str_to_time = crate::expressions::StrToTime {
37035                        this: Box::new((*e.this).clone()),
37036                        format: fmt.clone(),
37037                        zone: None,
37038                        safe: None,
37039                        target_type: None,
37040                    };
37041                    self.generate_str_to_time(&str_to_time)?;
37042                    self.write_keyword(" AS ");
37043                    self.write_keyword("DATE");
37044                    self.write(")");
37045                }
37046            }
37047        } else {
37048            // Without format (or default format): simple date conversion
37049            match self.config.dialect {
37050                Some(DialectType::MySQL)
37051                | Some(DialectType::SQLite)
37052                | Some(DialectType::StarRocks) => {
37053                    // MySQL/SQLite/StarRocks: DATE(x)
37054                    self.write_keyword("DATE");
37055                    self.write("(");
37056                    self.generate_expression(&e.this)?;
37057                    self.write(")");
37058                }
37059                Some(DialectType::Hive)
37060                | Some(DialectType::Spark)
37061                | Some(DialectType::Databricks)
37062                | Some(DialectType::Snowflake)
37063                | Some(DialectType::Doris) => {
37064                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37065                    self.write_keyword("TO_DATE");
37066                    self.write("(");
37067                    self.generate_expression(&e.this)?;
37068                    self.write(")");
37069                }
37070                Some(DialectType::Presto)
37071                | Some(DialectType::Trino)
37072                | Some(DialectType::Athena) => {
37073                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37074                    self.write_keyword("CAST");
37075                    self.write("(");
37076                    self.write_keyword("CAST");
37077                    self.write("(");
37078                    self.generate_expression(&e.this)?;
37079                    self.write_keyword(" AS ");
37080                    self.write_keyword("TIMESTAMP");
37081                    self.write(")");
37082                    self.write_keyword(" AS ");
37083                    self.write_keyword("DATE");
37084                    self.write(")");
37085                }
37086                Some(DialectType::ClickHouse) => {
37087                    // ClickHouse: CAST(x AS Nullable(DATE))
37088                    self.write_keyword("CAST");
37089                    self.write("(");
37090                    self.generate_expression(&e.this)?;
37091                    self.write_keyword(" AS ");
37092                    self.write("Nullable(DATE)");
37093                    self.write(")");
37094                }
37095                _ => {
37096                    // Default: CAST(x AS DATE)
37097                    self.write_keyword("CAST");
37098                    self.write("(");
37099                    self.generate_expression(&e.this)?;
37100                    self.write_keyword(" AS ");
37101                    self.write_keyword("DATE");
37102                    self.write(")");
37103                }
37104            }
37105        }
37106        Ok(())
37107    }
37108
37109    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37110        // TS_OR_DS_TO_TIME(this, [format])
37111        self.write_keyword("TS_OR_DS_TO_TIME");
37112        self.write("(");
37113        self.generate_expression(&e.this)?;
37114        if let Some(format) = &e.format {
37115            self.write(", '");
37116            self.write(format);
37117            self.write("'");
37118        }
37119        self.write(")");
37120        Ok(())
37121    }
37122
37123    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37124        // UNHEX(this, [expression])
37125        self.write_keyword("UNHEX");
37126        self.write("(");
37127        self.generate_expression(&e.this)?;
37128        if let Some(expression) = &e.expression {
37129            self.write(", ");
37130            self.generate_expression(expression)?;
37131        }
37132        self.write(")");
37133        Ok(())
37134    }
37135
37136    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37137        // U&this [UESCAPE escape]
37138        self.write("U&");
37139        self.generate_expression(&e.this)?;
37140        if let Some(escape) = &e.escape {
37141            self.write_space();
37142            self.write_keyword("UESCAPE");
37143            self.write_space();
37144            self.generate_expression(escape)?;
37145        }
37146        Ok(())
37147    }
37148
37149    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37150        // UNIFORM(this, expression, [gen], [seed])
37151        self.write_keyword("UNIFORM");
37152        self.write("(");
37153        self.generate_expression(&e.this)?;
37154        self.write(", ");
37155        self.generate_expression(&e.expression)?;
37156        if let Some(gen) = &e.gen {
37157            self.write(", ");
37158            self.generate_expression(gen)?;
37159        }
37160        if let Some(seed) = &e.seed {
37161            self.write(", ");
37162            self.generate_expression(seed)?;
37163        }
37164        self.write(")");
37165        Ok(())
37166    }
37167
37168    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37169        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37170        self.write_keyword("UNIQUE");
37171        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37172        if e.nulls.is_some() {
37173            self.write(" NULLS NOT DISTINCT");
37174        }
37175        if let Some(this) = &e.this {
37176            self.write_space();
37177            self.generate_expression(this)?;
37178        }
37179        if let Some(index_type) = &e.index_type {
37180            self.write(" USING ");
37181            self.generate_expression(index_type)?;
37182        }
37183        if let Some(on_conflict) = &e.on_conflict {
37184            self.write_space();
37185            self.generate_expression(on_conflict)?;
37186        }
37187        for opt in &e.options {
37188            self.write_space();
37189            self.generate_expression(opt)?;
37190        }
37191        Ok(())
37192    }
37193
37194    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37195        // UNIQUE KEY (expressions)
37196        self.write_keyword("UNIQUE KEY");
37197        self.write(" (");
37198        for (i, expr) in e.expressions.iter().enumerate() {
37199            if i > 0 {
37200                self.write(", ");
37201            }
37202            self.generate_expression(expr)?;
37203        }
37204        self.write(")");
37205        Ok(())
37206    }
37207
37208    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37209        // ROLLUP (r1(col1, col2), r2(col1))
37210        self.write_keyword("ROLLUP");
37211        self.write(" (");
37212        for (i, index) in e.expressions.iter().enumerate() {
37213            if i > 0 {
37214                self.write(", ");
37215            }
37216            self.generate_identifier(&index.name)?;
37217            self.write("(");
37218            for (j, col) in index.expressions.iter().enumerate() {
37219                if j > 0 {
37220                    self.write(", ");
37221                }
37222                self.generate_identifier(col)?;
37223            }
37224            self.write(")");
37225        }
37226        self.write(")");
37227        Ok(())
37228    }
37229
37230    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37231        match self.config.dialect {
37232            Some(DialectType::DuckDB) => {
37233                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37234                self.write_keyword("STRFTIME");
37235                self.write("(");
37236                self.write_keyword("TO_TIMESTAMP");
37237                self.write("(");
37238                self.generate_expression(&e.this)?;
37239                self.write("), '");
37240                if let Some(format) = &e.format {
37241                    self.write(format);
37242                }
37243                self.write("')");
37244            }
37245            Some(DialectType::Hive) => {
37246                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37247                self.write_keyword("FROM_UNIXTIME");
37248                self.write("(");
37249                self.generate_expression(&e.this)?;
37250                if let Some(format) = &e.format {
37251                    if format != "yyyy-MM-dd HH:mm:ss" {
37252                        self.write(", '");
37253                        self.write(format);
37254                        self.write("'");
37255                    }
37256                }
37257                self.write(")");
37258            }
37259            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37260                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37261                self.write_keyword("DATE_FORMAT");
37262                self.write("(");
37263                self.write_keyword("FROM_UNIXTIME");
37264                self.write("(");
37265                self.generate_expression(&e.this)?;
37266                self.write("), '");
37267                if let Some(format) = &e.format {
37268                    self.write(format);
37269                }
37270                self.write("')");
37271            }
37272            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37273                // Spark: FROM_UNIXTIME(value, format)
37274                self.write_keyword("FROM_UNIXTIME");
37275                self.write("(");
37276                self.generate_expression(&e.this)?;
37277                if let Some(format) = &e.format {
37278                    self.write(", '");
37279                    self.write(format);
37280                    self.write("'");
37281                }
37282                self.write(")");
37283            }
37284            _ => {
37285                // Default: UNIX_TO_STR(this, [format])
37286                self.write_keyword("UNIX_TO_STR");
37287                self.write("(");
37288                self.generate_expression(&e.this)?;
37289                if let Some(format) = &e.format {
37290                    self.write(", '");
37291                    self.write(format);
37292                    self.write("'");
37293                }
37294                self.write(")");
37295            }
37296        }
37297        Ok(())
37298    }
37299
37300    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37301        use crate::dialects::DialectType;
37302        let scale = e.scale.unwrap_or(0); // 0 = seconds
37303
37304        match self.config.dialect {
37305            Some(DialectType::Snowflake) => {
37306                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37307                self.write_keyword("TO_TIMESTAMP");
37308                self.write("(");
37309                self.generate_expression(&e.this)?;
37310                if let Some(s) = e.scale {
37311                    if s > 0 {
37312                        self.write(", ");
37313                        self.write(&s.to_string());
37314                    }
37315                }
37316                self.write(")");
37317            }
37318            Some(DialectType::BigQuery) => {
37319                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37320                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37321                match scale {
37322                    0 => {
37323                        self.write_keyword("TIMESTAMP_SECONDS");
37324                        self.write("(");
37325                        self.generate_expression(&e.this)?;
37326                        self.write(")");
37327                    }
37328                    3 => {
37329                        self.write_keyword("TIMESTAMP_MILLIS");
37330                        self.write("(");
37331                        self.generate_expression(&e.this)?;
37332                        self.write(")");
37333                    }
37334                    6 => {
37335                        self.write_keyword("TIMESTAMP_MICROS");
37336                        self.write("(");
37337                        self.generate_expression(&e.this)?;
37338                        self.write(")");
37339                    }
37340                    _ => {
37341                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37342                        self.write_keyword("TIMESTAMP_SECONDS");
37343                        self.write("(CAST(");
37344                        self.generate_expression(&e.this)?;
37345                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37346                    }
37347                }
37348            }
37349            Some(DialectType::Spark) => {
37350                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37351                // TIMESTAMP_MILLIS(value) for scale=3
37352                // TIMESTAMP_MICROS(value) for scale=6
37353                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37354                match scale {
37355                    0 => {
37356                        self.write_keyword("CAST");
37357                        self.write("(");
37358                        self.write_keyword("FROM_UNIXTIME");
37359                        self.write("(");
37360                        self.generate_expression(&e.this)?;
37361                        self.write(") ");
37362                        self.write_keyword("AS TIMESTAMP");
37363                        self.write(")");
37364                    }
37365                    3 => {
37366                        self.write_keyword("TIMESTAMP_MILLIS");
37367                        self.write("(");
37368                        self.generate_expression(&e.this)?;
37369                        self.write(")");
37370                    }
37371                    6 => {
37372                        self.write_keyword("TIMESTAMP_MICROS");
37373                        self.write("(");
37374                        self.generate_expression(&e.this)?;
37375                        self.write(")");
37376                    }
37377                    _ => {
37378                        self.write_keyword("TIMESTAMP_SECONDS");
37379                        self.write("(");
37380                        self.generate_expression(&e.this)?;
37381                        self.write(&format!(" / POWER(10, {}))", scale));
37382                    }
37383                }
37384            }
37385            Some(DialectType::Databricks) => {
37386                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37387                // TIMESTAMP_MILLIS(value) for scale=3
37388                // TIMESTAMP_MICROS(value) for scale=6
37389                match scale {
37390                    0 => {
37391                        self.write_keyword("CAST");
37392                        self.write("(");
37393                        self.write_keyword("FROM_UNIXTIME");
37394                        self.write("(");
37395                        self.generate_expression(&e.this)?;
37396                        self.write(") ");
37397                        self.write_keyword("AS TIMESTAMP");
37398                        self.write(")");
37399                    }
37400                    3 => {
37401                        self.write_keyword("TIMESTAMP_MILLIS");
37402                        self.write("(");
37403                        self.generate_expression(&e.this)?;
37404                        self.write(")");
37405                    }
37406                    6 => {
37407                        self.write_keyword("TIMESTAMP_MICROS");
37408                        self.write("(");
37409                        self.generate_expression(&e.this)?;
37410                        self.write(")");
37411                    }
37412                    _ => {
37413                        self.write_keyword("TIMESTAMP_SECONDS");
37414                        self.write("(");
37415                        self.generate_expression(&e.this)?;
37416                        self.write(&format!(" / POWER(10, {}))", scale));
37417                    }
37418                }
37419            }
37420            Some(DialectType::Hive) => {
37421                // Hive: FROM_UNIXTIME(value)
37422                if scale == 0 {
37423                    self.write_keyword("FROM_UNIXTIME");
37424                    self.write("(");
37425                    self.generate_expression(&e.this)?;
37426                    self.write(")");
37427                } else {
37428                    self.write_keyword("FROM_UNIXTIME");
37429                    self.write("(");
37430                    self.generate_expression(&e.this)?;
37431                    self.write(&format!(" / POWER(10, {})", scale));
37432                    self.write(")");
37433                }
37434            }
37435            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37436                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37437                // FROM_UNIXTIME(value) for scale=0
37438                if scale == 0 {
37439                    self.write_keyword("FROM_UNIXTIME");
37440                    self.write("(");
37441                    self.generate_expression(&e.this)?;
37442                    self.write(")");
37443                } else {
37444                    self.write_keyword("FROM_UNIXTIME");
37445                    self.write("(CAST(");
37446                    self.generate_expression(&e.this)?;
37447                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37448                }
37449            }
37450            Some(DialectType::DuckDB) => {
37451                // DuckDB: TO_TIMESTAMP(value) for scale=0
37452                // EPOCH_MS(value) for scale=3
37453                // MAKE_TIMESTAMP(value) for scale=6
37454                match scale {
37455                    0 => {
37456                        self.write_keyword("TO_TIMESTAMP");
37457                        self.write("(");
37458                        self.generate_expression(&e.this)?;
37459                        self.write(")");
37460                    }
37461                    3 => {
37462                        self.write_keyword("EPOCH_MS");
37463                        self.write("(");
37464                        self.generate_expression(&e.this)?;
37465                        self.write(")");
37466                    }
37467                    6 => {
37468                        self.write_keyword("MAKE_TIMESTAMP");
37469                        self.write("(");
37470                        self.generate_expression(&e.this)?;
37471                        self.write(")");
37472                    }
37473                    _ => {
37474                        self.write_keyword("TO_TIMESTAMP");
37475                        self.write("(");
37476                        self.generate_expression(&e.this)?;
37477                        self.write(&format!(" / POWER(10, {}))", scale));
37478                        self.write_keyword(" AT TIME ZONE");
37479                        self.write(" 'UTC'");
37480                    }
37481                }
37482            }
37483            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37484                // Doris/StarRocks: FROM_UNIXTIME(value)
37485                self.write_keyword("FROM_UNIXTIME");
37486                self.write("(");
37487                self.generate_expression(&e.this)?;
37488                self.write(")");
37489            }
37490            Some(DialectType::Oracle) => {
37491                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
37492                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
37493                self.generate_expression(&e.this)?;
37494                self.write(" / 86400)");
37495            }
37496            Some(DialectType::Redshift) => {
37497                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
37498                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
37499                self.write("(TIMESTAMP 'epoch' + ");
37500                if scale == 0 {
37501                    self.generate_expression(&e.this)?;
37502                } else {
37503                    self.write("(");
37504                    self.generate_expression(&e.this)?;
37505                    self.write(&format!(" / POWER(10, {}))", scale));
37506                }
37507                self.write(" * INTERVAL '1 SECOND')");
37508            }
37509            Some(DialectType::Exasol) => {
37510                // Exasol: FROM_POSIX_TIME(value)
37511                self.write_keyword("FROM_POSIX_TIME");
37512                self.write("(");
37513                self.generate_expression(&e.this)?;
37514                self.write(")");
37515            }
37516            _ => {
37517                // Default: TO_TIMESTAMP(value[, scale])
37518                self.write_keyword("TO_TIMESTAMP");
37519                self.write("(");
37520                self.generate_expression(&e.this)?;
37521                if let Some(s) = e.scale {
37522                    self.write(", ");
37523                    self.write(&s.to_string());
37524                }
37525                self.write(")");
37526            }
37527        }
37528        Ok(())
37529    }
37530
37531    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
37532        // NAME col VALUE col1, col2, ...
37533        if !matches!(&*e.this, Expression::Null(_)) {
37534            self.write_keyword("NAME");
37535            self.write_space();
37536            self.generate_expression(&e.this)?;
37537        }
37538        if !e.expressions.is_empty() {
37539            self.write_space();
37540            self.write_keyword("VALUE");
37541            self.write_space();
37542            for (i, expr) in e.expressions.iter().enumerate() {
37543                if i > 0 {
37544                    self.write(", ");
37545                }
37546                self.generate_expression(expr)?;
37547            }
37548        }
37549        Ok(())
37550    }
37551
37552    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
37553        // this(expressions) or (this)(expressions)
37554        if e.wrapped.is_some() {
37555            self.write("(");
37556        }
37557        self.generate_expression(&e.this)?;
37558        if e.wrapped.is_some() {
37559            self.write(")");
37560        }
37561        self.write("(");
37562        for (i, expr) in e.expressions.iter().enumerate() {
37563            if i > 0 {
37564                self.write(", ");
37565            }
37566            self.generate_expression(expr)?;
37567        }
37568        self.write(")");
37569        Ok(())
37570    }
37571
37572    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
37573        // USING TEMPLATE this
37574        self.write_keyword("USING TEMPLATE");
37575        self.write_space();
37576        self.generate_expression(&e.this)?;
37577        Ok(())
37578    }
37579
37580    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
37581        // UTC_TIME
37582        self.write_keyword("UTC_TIME");
37583        Ok(())
37584    }
37585
37586    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
37587        if matches!(
37588            self.config.dialect,
37589            Some(crate::dialects::DialectType::ClickHouse)
37590        ) {
37591            self.write_keyword("CURRENT_TIMESTAMP");
37592            self.write("('UTC')");
37593        } else {
37594            self.write_keyword("UTC_TIMESTAMP");
37595        }
37596        Ok(())
37597    }
37598
37599    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
37600        use crate::dialects::DialectType;
37601        // Choose UUID function name based on target dialect
37602        let func_name = match self.config.dialect {
37603            Some(DialectType::Snowflake) => "UUID_STRING",
37604            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
37605            Some(DialectType::BigQuery) => "GENERATE_UUID",
37606            _ => {
37607                if let Some(name) = &e.name {
37608                    name.as_str()
37609                } else {
37610                    "UUID"
37611                }
37612            }
37613        };
37614        self.write_keyword(func_name);
37615        self.write("(");
37616        if let Some(this) = &e.this {
37617            self.generate_expression(this)?;
37618        }
37619        self.write(")");
37620        Ok(())
37621    }
37622
37623    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37624        // MAP(key1, value1, key2, value2, ...)
37625        self.write_keyword("MAP");
37626        self.write("(");
37627        let mut first = true;
37628        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37629            if !first {
37630                self.write(", ");
37631            }
37632            self.generate_expression(k)?;
37633            self.write(", ");
37634            self.generate_expression(v)?;
37635            first = false;
37636        }
37637        self.write(")");
37638        Ok(())
37639    }
37640
37641    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37642        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37643        self.write_keyword("VECTOR_SEARCH");
37644        self.write("(");
37645        self.generate_expression(&e.this)?;
37646        if let Some(col) = &e.column_to_search {
37647            self.write(", ");
37648            self.generate_expression(col)?;
37649        }
37650        if let Some(query_table) = &e.query_table {
37651            self.write(", ");
37652            self.generate_expression(query_table)?;
37653        }
37654        if let Some(query_col) = &e.query_column_to_search {
37655            self.write(", ");
37656            self.generate_expression(query_col)?;
37657        }
37658        if let Some(top_k) = &e.top_k {
37659            self.write(", ");
37660            self.generate_expression(top_k)?;
37661        }
37662        if let Some(dist_type) = &e.distance_type {
37663            self.write(", ");
37664            self.generate_expression(dist_type)?;
37665        }
37666        self.write(")");
37667        Ok(())
37668    }
37669
37670    fn generate_version(&mut self, e: &Version) -> Result<()> {
37671        // Python: f"FOR {expression.name} {kind} {expr}"
37672        // e.this = Identifier("TIMESTAMP" or "VERSION")
37673        // e.kind = "AS OF" (or "BETWEEN", etc.)
37674        // e.expression = the value expression
37675        // Hive does NOT use the FOR prefix for time travel
37676        use crate::dialects::DialectType;
37677        let skip_for = matches!(
37678            self.config.dialect,
37679            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37680        );
37681        if !skip_for {
37682            self.write_keyword("FOR");
37683            self.write_space();
37684        }
37685        // Extract the name from this (which is an Identifier expression)
37686        match e.this.as_ref() {
37687            Expression::Identifier(ident) => {
37688                self.write_keyword(&ident.name);
37689            }
37690            _ => {
37691                self.generate_expression(&e.this)?;
37692            }
37693        }
37694        self.write_space();
37695        self.write_keyword(&e.kind);
37696        if let Some(expression) = &e.expression {
37697            self.write_space();
37698            self.generate_expression(expression)?;
37699        }
37700        Ok(())
37701    }
37702
37703    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37704        // Python: return self.sql(expression, "this")
37705        self.generate_expression(&e.this)?;
37706        Ok(())
37707    }
37708
37709    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37710        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37711        if e.this.is_some() {
37712            self.write_keyword("NOT VOLATILE");
37713        } else {
37714            self.write_keyword("VOLATILE");
37715        }
37716        Ok(())
37717    }
37718
37719    fn generate_watermark_column_constraint(
37720        &mut self,
37721        e: &WatermarkColumnConstraint,
37722    ) -> Result<()> {
37723        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37724        self.write_keyword("WATERMARK FOR");
37725        self.write_space();
37726        self.generate_expression(&e.this)?;
37727        self.write_space();
37728        self.write_keyword("AS");
37729        self.write_space();
37730        self.generate_expression(&e.expression)?;
37731        Ok(())
37732    }
37733
37734    fn generate_week(&mut self, e: &Week) -> Result<()> {
37735        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37736        self.write_keyword("WEEK");
37737        self.write("(");
37738        self.generate_expression(&e.this)?;
37739        if let Some(mode) = &e.mode {
37740            self.write(", ");
37741            self.generate_expression(mode)?;
37742        }
37743        self.write(")");
37744        Ok(())
37745    }
37746
37747    fn generate_when(&mut self, e: &When) -> Result<()> {
37748        // Python: WHEN {matched}{source}{condition} THEN {then}
37749        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37750        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37751        self.write_keyword("WHEN");
37752        self.write_space();
37753
37754        // Check if matched
37755        if let Some(matched) = &e.matched {
37756            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37757            match matched.as_ref() {
37758                Expression::Boolean(b) if b.value => {
37759                    self.write_keyword("MATCHED");
37760                }
37761                _ => {
37762                    self.write_keyword("NOT MATCHED");
37763                }
37764            }
37765        } else {
37766            self.write_keyword("NOT MATCHED");
37767        }
37768
37769        // BY SOURCE / BY TARGET
37770        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37771        // BY TARGET is the default and typically omitted in output
37772        // Only emit if the dialect supports BY SOURCE syntax
37773        if self.config.matched_by_source {
37774            if let Some(source) = &e.source {
37775                if let Expression::Boolean(b) = source.as_ref() {
37776                    if b.value {
37777                        // BY SOURCE
37778                        self.write_space();
37779                        self.write_keyword("BY SOURCE");
37780                    }
37781                    // BY TARGET (b.value == false) is omitted as it's the default
37782                } else {
37783                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37784                    self.write_space();
37785                    self.write_keyword("BY SOURCE");
37786                }
37787            }
37788        }
37789
37790        // Condition
37791        if let Some(condition) = &e.condition {
37792            self.write_space();
37793            self.write_keyword("AND");
37794            self.write_space();
37795            self.generate_expression(condition)?;
37796        }
37797
37798        self.write_space();
37799        self.write_keyword("THEN");
37800        self.write_space();
37801
37802        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37803        // MERGE actions are stored as Tuples with the action keyword as first element
37804        self.generate_merge_action(&e.then)?;
37805
37806        Ok(())
37807    }
37808
37809    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37810        match action {
37811            Expression::Tuple(tuple) => {
37812                let elements = &tuple.expressions;
37813                if elements.is_empty() {
37814                    return self.generate_expression(action);
37815                }
37816                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37817                match &elements[0] {
37818                    Expression::Var(v) if v.this == "INSERT" => {
37819                        self.write_keyword("INSERT");
37820                        // Spark: INSERT * (insert all columns)
37821                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37822                            self.write(" *");
37823                            if let Some(Expression::Where(w)) = elements.get(2) {
37824                                self.write_space();
37825                                self.generate_where(w)?;
37826                            }
37827                        } else {
37828                            let mut values_idx = 1;
37829                            // Check if second element is column list (Tuple)
37830                            if elements.len() > 1 {
37831                                if let Expression::Tuple(cols) = &elements[1] {
37832                                    // Could be columns or values - if there's a third element, second is columns
37833                                    if elements.len() > 2 {
37834                                        // Second is columns, third is values
37835                                        self.write(" (");
37836                                        for (i, col) in cols.expressions.iter().enumerate() {
37837                                            if i > 0 {
37838                                                self.write(", ");
37839                                            }
37840                                            // Strip MERGE target qualifiers from INSERT column list
37841                                            if !self.merge_strip_qualifiers.is_empty() {
37842                                                let stripped = self.strip_merge_qualifier(col);
37843                                                self.generate_expression(&stripped)?;
37844                                            } else {
37845                                                self.generate_expression(col)?;
37846                                            }
37847                                        }
37848                                        self.write(")");
37849                                        values_idx = 2;
37850                                    } else {
37851                                        // Only two elements: INSERT + values (no explicit columns)
37852                                        values_idx = 1;
37853                                    }
37854                                }
37855                            }
37856                            let mut next_idx = values_idx;
37857                            // Generate VALUES clause
37858                            if values_idx < elements.len()
37859                                && !matches!(&elements[values_idx], Expression::Where(_))
37860                            {
37861                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37862                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37863                                if !is_row {
37864                                    self.write_space();
37865                                    self.write_keyword("VALUES");
37866                                }
37867                                self.write(" ");
37868                                if let Expression::Tuple(vals) = &elements[values_idx] {
37869                                    self.write("(");
37870                                    for (i, val) in vals.expressions.iter().enumerate() {
37871                                        if i > 0 {
37872                                            self.write(", ");
37873                                        }
37874                                        self.generate_expression(val)?;
37875                                    }
37876                                    self.write(")");
37877                                } else {
37878                                    self.generate_expression(&elements[values_idx])?;
37879                                }
37880                                next_idx += 1;
37881                            }
37882                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
37883                                self.write_space();
37884                                self.generate_where(w)?;
37885                            }
37886                        } // close else for INSERT * check
37887                    }
37888                    Expression::Var(v) if v.this == "UPDATE" => {
37889                        self.write_keyword("UPDATE");
37890                        // Spark: UPDATE * (update all columns)
37891                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37892                            self.write(" *");
37893                            if let Some(Expression::Where(w)) = elements.get(2) {
37894                                self.write_space();
37895                                self.generate_where(w)?;
37896                            }
37897                        } else if elements.len() > 1 {
37898                            self.write_space();
37899                            self.write_keyword("SET");
37900                            // In pretty mode, put assignments on next line with extra indent
37901                            if self.config.pretty {
37902                                self.write_newline();
37903                                self.indent_level += 1;
37904                                self.write_indent();
37905                            } else {
37906                                self.write_space();
37907                            }
37908                            if let Expression::Tuple(assignments) = &elements[1] {
37909                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37910                                    if i > 0 {
37911                                        if self.config.pretty {
37912                                            self.write(",");
37913                                            self.write_newline();
37914                                            self.write_indent();
37915                                        } else {
37916                                            self.write(", ");
37917                                        }
37918                                    }
37919                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37920                                    if !self.merge_strip_qualifiers.is_empty() {
37921                                        self.generate_merge_set_assignment(assignment)?;
37922                                    } else {
37923                                        self.generate_expression(assignment)?;
37924                                    }
37925                                }
37926                            } else {
37927                                self.generate_expression(&elements[1])?;
37928                            }
37929                            if self.config.pretty {
37930                                self.indent_level -= 1;
37931                            }
37932                            if let Some(Expression::Where(w)) = elements.get(2) {
37933                                self.write_space();
37934                                self.generate_where(w)?;
37935                            }
37936                        }
37937                    }
37938                    Expression::Var(v) if v.this == "DELETE" => {
37939                        self.write_keyword("DELETE");
37940                        if let Some(Expression::Where(w)) = elements.get(1) {
37941                            self.write_space();
37942                            self.generate_where(w)?;
37943                        }
37944                    }
37945                    _ => {
37946                        // Fallback: generic tuple generation
37947                        self.generate_expression(action)?;
37948                    }
37949                }
37950            }
37951            Expression::Var(v)
37952                if v.this == "INSERT"
37953                    || v.this == "UPDATE"
37954                    || v.this == "DELETE"
37955                    || v.this == "DO NOTHING" =>
37956            {
37957                self.write_keyword(&v.this);
37958            }
37959            _ => {
37960                self.generate_expression(action)?;
37961            }
37962        }
37963        Ok(())
37964    }
37965
37966    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37967    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37968        match assignment {
37969            Expression::Eq(eq) => {
37970                // Strip qualifier from the left side if it matches a MERGE target name
37971                let stripped_left = self.strip_merge_qualifier(&eq.left);
37972                self.generate_expression(&stripped_left)?;
37973                self.write(" = ");
37974                self.generate_expression(&eq.right)?;
37975                Ok(())
37976            }
37977            other => self.generate_expression(other),
37978        }
37979    }
37980
37981    /// Strip table qualifier from a column reference if it matches a MERGE target name
37982    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37983        match expr {
37984            Expression::Column(col) => {
37985                if let Some(ref table_ident) = col.table {
37986                    if self
37987                        .merge_strip_qualifiers
37988                        .iter()
37989                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37990                    {
37991                        // Strip the table qualifier
37992                        let mut col = col.clone();
37993                        col.table = None;
37994                        return Expression::Column(col);
37995                    }
37996                }
37997                expr.clone()
37998            }
37999            Expression::Dot(dot) => {
38000                // table.column -> column (strip qualifier)
38001                if let Expression::Identifier(id) = &dot.this {
38002                    if self
38003                        .merge_strip_qualifiers
38004                        .iter()
38005                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38006                    {
38007                        return Expression::Identifier(dot.field.clone());
38008                    }
38009                }
38010                expr.clone()
38011            }
38012            _ => expr.clone(),
38013        }
38014    }
38015
38016    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38017        // Python: return self.expressions(expression, sep=" ", indent=False)
38018        for (i, expr) in e.expressions.iter().enumerate() {
38019            if i > 0 {
38020                // In pretty mode, each WHEN clause on its own line
38021                if self.config.pretty {
38022                    self.write_newline();
38023                    self.write_indent();
38024                } else {
38025                    self.write_space();
38026                }
38027            }
38028            self.generate_expression(expr)?;
38029        }
38030        Ok(())
38031    }
38032
38033    fn generate_where(&mut self, e: &Where) -> Result<()> {
38034        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38035        self.write_keyword("WHERE");
38036        self.write_space();
38037        self.generate_expression(&e.this)?;
38038        Ok(())
38039    }
38040
38041    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38042        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38043        self.write_keyword("WIDTH_BUCKET");
38044        self.write("(");
38045        self.generate_expression(&e.this)?;
38046        if let Some(min_value) = &e.min_value {
38047            self.write(", ");
38048            self.generate_expression(min_value)?;
38049        }
38050        if let Some(max_value) = &e.max_value {
38051            self.write(", ");
38052            self.generate_expression(max_value)?;
38053        }
38054        if let Some(num_buckets) = &e.num_buckets {
38055            self.write(", ");
38056            self.generate_expression(num_buckets)?;
38057        }
38058        self.write(")");
38059        Ok(())
38060    }
38061
38062    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38063        // Window specification: PARTITION BY ... ORDER BY ... frame
38064        self.generate_window_spec(e)
38065    }
38066
38067    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38068        // Window specification: PARTITION BY ... ORDER BY ... frame
38069        let mut has_content = false;
38070
38071        // PARTITION BY
38072        if !e.partition_by.is_empty() {
38073            self.write_keyword("PARTITION BY");
38074            self.write_space();
38075            for (i, expr) in e.partition_by.iter().enumerate() {
38076                if i > 0 {
38077                    self.write(", ");
38078                }
38079                self.generate_expression(expr)?;
38080            }
38081            has_content = true;
38082        }
38083
38084        // ORDER BY
38085        if !e.order_by.is_empty() {
38086            if has_content {
38087                self.write_space();
38088            }
38089            self.write_keyword("ORDER BY");
38090            self.write_space();
38091            for (i, ordered) in e.order_by.iter().enumerate() {
38092                if i > 0 {
38093                    self.write(", ");
38094                }
38095                self.generate_expression(&ordered.this)?;
38096                if ordered.desc {
38097                    self.write_space();
38098                    self.write_keyword("DESC");
38099                } else if ordered.explicit_asc {
38100                    self.write_space();
38101                    self.write_keyword("ASC");
38102                }
38103                if let Some(nulls_first) = ordered.nulls_first {
38104                    self.write_space();
38105                    self.write_keyword("NULLS");
38106                    self.write_space();
38107                    if nulls_first {
38108                        self.write_keyword("FIRST");
38109                    } else {
38110                        self.write_keyword("LAST");
38111                    }
38112                }
38113            }
38114            has_content = true;
38115        }
38116
38117        // Frame specification
38118        if let Some(frame) = &e.frame {
38119            if has_content {
38120                self.write_space();
38121            }
38122            self.generate_window_frame(frame)?;
38123        }
38124
38125        Ok(())
38126    }
38127
38128    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38129        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38130        self.write_keyword("WITH");
38131        self.write_space();
38132        if e.no.is_some() {
38133            self.write_keyword("NO");
38134            self.write_space();
38135        }
38136        self.write_keyword("DATA");
38137
38138        // statistics
38139        if let Some(statistics) = &e.statistics {
38140            self.write_space();
38141            self.write_keyword("AND");
38142            self.write_space();
38143            // Check if statistics is true or false
38144            match statistics.as_ref() {
38145                Expression::Boolean(b) if !b.value => {
38146                    self.write_keyword("NO");
38147                    self.write_space();
38148                }
38149                _ => {}
38150            }
38151            self.write_keyword("STATISTICS");
38152        }
38153        Ok(())
38154    }
38155
38156    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38157        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38158        self.write_keyword("WITH FILL");
38159
38160        if let Some(from_) = &e.from_ {
38161            self.write_space();
38162            self.write_keyword("FROM");
38163            self.write_space();
38164            self.generate_expression(from_)?;
38165        }
38166
38167        if let Some(to) = &e.to {
38168            self.write_space();
38169            self.write_keyword("TO");
38170            self.write_space();
38171            self.generate_expression(to)?;
38172        }
38173
38174        if let Some(step) = &e.step {
38175            self.write_space();
38176            self.write_keyword("STEP");
38177            self.write_space();
38178            self.generate_expression(step)?;
38179        }
38180
38181        if let Some(staleness) = &e.staleness {
38182            self.write_space();
38183            self.write_keyword("STALENESS");
38184            self.write_space();
38185            self.generate_expression(staleness)?;
38186        }
38187
38188        if let Some(interpolate) = &e.interpolate {
38189            self.write_space();
38190            self.write_keyword("INTERPOLATE");
38191            self.write(" (");
38192            // INTERPOLATE items use reversed alias format: name AS expression
38193            self.generate_interpolate_item(interpolate)?;
38194            self.write(")");
38195        }
38196
38197        Ok(())
38198    }
38199
38200    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38201    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38202        match expr {
38203            Expression::Alias(alias) => {
38204                // Output as: alias_name AS expression
38205                self.generate_identifier(&alias.alias)?;
38206                self.write_space();
38207                self.write_keyword("AS");
38208                self.write_space();
38209                self.generate_expression(&alias.this)?;
38210            }
38211            Expression::Tuple(tuple) => {
38212                for (i, item) in tuple.expressions.iter().enumerate() {
38213                    if i > 0 {
38214                        self.write(", ");
38215                    }
38216                    self.generate_interpolate_item(item)?;
38217                }
38218            }
38219            other => {
38220                self.generate_expression(other)?;
38221            }
38222        }
38223        Ok(())
38224    }
38225
38226    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38227        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38228        self.write_keyword("WITH JOURNAL TABLE");
38229        self.write("=");
38230        self.generate_expression(&e.this)?;
38231        Ok(())
38232    }
38233
38234    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38235        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38236        self.generate_expression(&e.this)?;
38237        self.write_space();
38238        self.write_keyword("WITH");
38239        self.write_space();
38240        self.write_keyword(&e.op);
38241        Ok(())
38242    }
38243
38244    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38245        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38246        self.write_keyword("WITH");
38247        self.write_space();
38248        for (i, expr) in e.expressions.iter().enumerate() {
38249            if i > 0 {
38250                self.write(", ");
38251            }
38252            self.generate_expression(expr)?;
38253        }
38254        Ok(())
38255    }
38256
38257    fn generate_with_schema_binding_property(
38258        &mut self,
38259        e: &WithSchemaBindingProperty,
38260    ) -> Result<()> {
38261        // Python: return f"WITH {self.sql(expression, 'this')}"
38262        self.write_keyword("WITH");
38263        self.write_space();
38264        self.generate_expression(&e.this)?;
38265        Ok(())
38266    }
38267
38268    fn generate_with_system_versioning_property(
38269        &mut self,
38270        e: &WithSystemVersioningProperty,
38271    ) -> Result<()> {
38272        // Python: complex logic for SYSTEM_VERSIONING with options
38273        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38274        // or SYSTEM_VERSIONING=ON/OFF
38275        // with WITH(...) wrapper if with_ is set
38276
38277        let mut parts = Vec::new();
38278
38279        if let Some(this) = &e.this {
38280            // HISTORY_TABLE=...
38281            let mut s = String::from("HISTORY_TABLE=");
38282            let mut gen = Generator::new();
38283            gen.generate_expression(this)?;
38284            s.push_str(&gen.output);
38285            parts.push(s);
38286        }
38287
38288        if let Some(data_consistency) = &e.data_consistency {
38289            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38290            let mut gen = Generator::new();
38291            gen.generate_expression(data_consistency)?;
38292            s.push_str(&gen.output);
38293            parts.push(s);
38294        }
38295
38296        if let Some(retention_period) = &e.retention_period {
38297            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38298            let mut gen = Generator::new();
38299            gen.generate_expression(retention_period)?;
38300            s.push_str(&gen.output);
38301            parts.push(s);
38302        }
38303
38304        self.write_keyword("SYSTEM_VERSIONING");
38305        self.write("=");
38306
38307        if !parts.is_empty() {
38308            self.write_keyword("ON");
38309            self.write("(");
38310            self.write(&parts.join(", "));
38311            self.write(")");
38312        } else if e.on.is_some() {
38313            self.write_keyword("ON");
38314        } else {
38315            self.write_keyword("OFF");
38316        }
38317
38318        // Wrap in WITH(...) if with_ is set
38319        if e.with_.is_some() {
38320            let inner = self.output.clone();
38321            self.output.clear();
38322            self.write("WITH(");
38323            self.write(&inner);
38324            self.write(")");
38325        }
38326
38327        Ok(())
38328    }
38329
38330    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38331        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38332        self.write_keyword("WITH");
38333        self.write(" (");
38334        for (i, expr) in e.expressions.iter().enumerate() {
38335            if i > 0 {
38336                self.write(", ");
38337            }
38338            self.generate_expression(expr)?;
38339        }
38340        self.write(")");
38341        Ok(())
38342    }
38343
38344    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38345        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38346        // return self.func("XMLELEMENT", name, *expression.expressions)
38347        self.write_keyword("XMLELEMENT");
38348        self.write("(");
38349
38350        if e.evalname.is_some() {
38351            self.write_keyword("EVALNAME");
38352        } else {
38353            self.write_keyword("NAME");
38354        }
38355        self.write_space();
38356        self.generate_expression(&e.this)?;
38357
38358        for expr in &e.expressions {
38359            self.write(", ");
38360            self.generate_expression(expr)?;
38361        }
38362        self.write(")");
38363        Ok(())
38364    }
38365
38366    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38367        // XMLGET(this, expression [, instance])
38368        self.write_keyword("XMLGET");
38369        self.write("(");
38370        self.generate_expression(&e.this)?;
38371        self.write(", ");
38372        self.generate_expression(&e.expression)?;
38373        if let Some(instance) = &e.instance {
38374            self.write(", ");
38375            self.generate_expression(instance)?;
38376        }
38377        self.write(")");
38378        Ok(())
38379    }
38380
38381    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38382        // Python: this + optional (expr)
38383        self.generate_expression(&e.this)?;
38384        if let Some(expression) = &e.expression {
38385            self.write("(");
38386            self.generate_expression(expression)?;
38387            self.write(")");
38388        }
38389        Ok(())
38390    }
38391
38392    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38393        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38394        self.write_keyword("XMLTABLE");
38395        self.write("(");
38396
38397        if self.config.pretty {
38398            self.indent_level += 1;
38399            self.write_newline();
38400            self.write_indent();
38401            self.generate_expression(&e.this)?;
38402
38403            if let Some(passing) = &e.passing {
38404                self.write_newline();
38405                self.write_indent();
38406                self.write_keyword("PASSING");
38407                if let Expression::Tuple(tuple) = passing.as_ref() {
38408                    for expr in &tuple.expressions {
38409                        self.write_newline();
38410                        self.indent_level += 1;
38411                        self.write_indent();
38412                        self.generate_expression(expr)?;
38413                        self.indent_level -= 1;
38414                    }
38415                } else {
38416                    self.write_newline();
38417                    self.indent_level += 1;
38418                    self.write_indent();
38419                    self.generate_expression(passing)?;
38420                    self.indent_level -= 1;
38421                }
38422            }
38423
38424            if e.by_ref.is_some() {
38425                self.write_newline();
38426                self.write_indent();
38427                self.write_keyword("RETURNING SEQUENCE BY REF");
38428            }
38429
38430            if !e.columns.is_empty() {
38431                self.write_newline();
38432                self.write_indent();
38433                self.write_keyword("COLUMNS");
38434                for (i, col) in e.columns.iter().enumerate() {
38435                    self.write_newline();
38436                    self.indent_level += 1;
38437                    self.write_indent();
38438                    self.generate_expression(col)?;
38439                    self.indent_level -= 1;
38440                    if i < e.columns.len() - 1 {
38441                        self.write(",");
38442                    }
38443                }
38444            }
38445
38446            self.indent_level -= 1;
38447            self.write_newline();
38448            self.write_indent();
38449            self.write(")");
38450            return Ok(());
38451        }
38452
38453        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38454        if let Some(namespaces) = &e.namespaces {
38455            self.write_keyword("XMLNAMESPACES");
38456            self.write("(");
38457            // Unwrap Tuple if present to avoid extra parentheses
38458            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38459                for (i, expr) in tuple.expressions.iter().enumerate() {
38460                    if i > 0 {
38461                        self.write(", ");
38462                    }
38463                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38464                    // See xmlnamespace_sql in generator.py
38465                    if !matches!(expr, Expression::Alias(_)) {
38466                        self.write_keyword("DEFAULT");
38467                        self.write_space();
38468                    }
38469                    self.generate_expression(expr)?;
38470                }
38471            } else {
38472                // Single namespace - check if DEFAULT
38473                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
38474                    self.write_keyword("DEFAULT");
38475                    self.write_space();
38476                }
38477                self.generate_expression(namespaces)?;
38478            }
38479            self.write("), ");
38480        }
38481
38482        // XPath expression
38483        self.generate_expression(&e.this)?;
38484
38485        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
38486        if let Some(passing) = &e.passing {
38487            self.write_space();
38488            self.write_keyword("PASSING");
38489            self.write_space();
38490            // Unwrap Tuple if present to avoid extra parentheses
38491            if let Expression::Tuple(tuple) = passing.as_ref() {
38492                for (i, expr) in tuple.expressions.iter().enumerate() {
38493                    if i > 0 {
38494                        self.write(", ");
38495                    }
38496                    self.generate_expression(expr)?;
38497                }
38498            } else {
38499                self.generate_expression(passing)?;
38500            }
38501        }
38502
38503        // RETURNING SEQUENCE BY REF
38504        if e.by_ref.is_some() {
38505            self.write_space();
38506            self.write_keyword("RETURNING SEQUENCE BY REF");
38507        }
38508
38509        // COLUMNS clause
38510        if !e.columns.is_empty() {
38511            self.write_space();
38512            self.write_keyword("COLUMNS");
38513            self.write_space();
38514            for (i, col) in e.columns.iter().enumerate() {
38515                if i > 0 {
38516                    self.write(", ");
38517                }
38518                self.generate_expression(col)?;
38519            }
38520        }
38521
38522        self.write(")");
38523        Ok(())
38524    }
38525
38526    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
38527        // Python: return self.connector_sql(expression, "XOR", stack)
38528        // Handles: this XOR expression or expressions joined by XOR
38529        if let Some(this) = &e.this {
38530            self.generate_expression(this)?;
38531            if let Some(expression) = &e.expression {
38532                self.write_space();
38533                self.write_keyword("XOR");
38534                self.write_space();
38535                self.generate_expression(expression)?;
38536            }
38537        }
38538
38539        // Handle multiple expressions
38540        for (i, expr) in e.expressions.iter().enumerate() {
38541            if i > 0 || e.this.is_some() {
38542                self.write_space();
38543                self.write_keyword("XOR");
38544                self.write_space();
38545            }
38546            self.generate_expression(expr)?;
38547        }
38548        Ok(())
38549    }
38550
38551    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
38552        // ZIPF(this, elementcount [, gen])
38553        self.write_keyword("ZIPF");
38554        self.write("(");
38555        self.generate_expression(&e.this)?;
38556        if let Some(elementcount) = &e.elementcount {
38557            self.write(", ");
38558            self.generate_expression(elementcount)?;
38559        }
38560        if let Some(gen) = &e.gen {
38561            self.write(", ");
38562            self.generate_expression(gen)?;
38563        }
38564        self.write(")");
38565        Ok(())
38566    }
38567}
38568
38569impl Default for Generator {
38570    fn default() -> Self {
38571        Self::new()
38572    }
38573}
38574
38575#[cfg(test)]
38576mod tests {
38577    use super::*;
38578    use crate::parser::Parser;
38579
38580    fn roundtrip(sql: &str) -> String {
38581        let ast = Parser::parse_sql(sql).unwrap();
38582        Generator::sql(&ast[0]).unwrap()
38583    }
38584
38585    #[test]
38586    fn test_simple_select() {
38587        let result = roundtrip("SELECT 1");
38588        assert_eq!(result, "SELECT 1");
38589    }
38590
38591    #[test]
38592    fn test_select_from() {
38593        let result = roundtrip("SELECT a, b FROM t");
38594        assert_eq!(result, "SELECT a, b FROM t");
38595    }
38596
38597    #[test]
38598    fn test_select_where() {
38599        let result = roundtrip("SELECT * FROM t WHERE x = 1");
38600        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
38601    }
38602
38603    #[test]
38604    fn test_select_join() {
38605        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
38606        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
38607    }
38608
38609    #[test]
38610    fn test_insert() {
38611        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
38612        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
38613    }
38614
38615    #[test]
38616    fn test_pretty_print() {
38617        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
38618        let result = Generator::pretty_sql(&ast[0]).unwrap();
38619        assert!(result.contains('\n'));
38620    }
38621
38622    #[test]
38623    fn test_window_function() {
38624        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
38625        assert_eq!(
38626            result,
38627            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
38628        );
38629    }
38630
38631    #[test]
38632    fn test_window_function_with_frame() {
38633        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38634        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
38635    }
38636
38637    #[test]
38638    fn test_aggregate_with_filter() {
38639        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
38640        assert_eq!(
38641            result,
38642            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38643        );
38644    }
38645
38646    #[test]
38647    fn test_subscript() {
38648        let result = roundtrip("SELECT arr[0]");
38649        assert_eq!(result, "SELECT arr[0]");
38650    }
38651
38652    // DDL tests
38653    #[test]
38654    fn test_create_table() {
38655        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38656        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38657    }
38658
38659    #[test]
38660    fn test_create_table_with_constraints() {
38661        let result = roundtrip(
38662            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38663        );
38664        assert_eq!(
38665            result,
38666            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38667        );
38668    }
38669
38670    #[test]
38671    fn test_create_table_if_not_exists() {
38672        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38673        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38674    }
38675
38676    #[test]
38677    fn test_drop_table() {
38678        let result = roundtrip("DROP TABLE users");
38679        assert_eq!(result, "DROP TABLE users");
38680    }
38681
38682    #[test]
38683    fn test_drop_table_if_exists_cascade() {
38684        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38685        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38686    }
38687
38688    #[test]
38689    fn test_alter_table_add_column() {
38690        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38691        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38692    }
38693
38694    #[test]
38695    fn test_alter_table_drop_column() {
38696        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38697        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38698    }
38699
38700    #[test]
38701    fn test_create_index() {
38702        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38703        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38704    }
38705
38706    #[test]
38707    fn test_create_unique_index() {
38708        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38709        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38710    }
38711
38712    #[test]
38713    fn test_drop_index() {
38714        let result = roundtrip("DROP INDEX idx_name");
38715        assert_eq!(result, "DROP INDEX idx_name");
38716    }
38717
38718    #[test]
38719    fn test_create_view() {
38720        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38721        assert_eq!(
38722            result,
38723            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38724        );
38725    }
38726
38727    #[test]
38728    fn test_drop_view() {
38729        let result = roundtrip("DROP VIEW active_users");
38730        assert_eq!(result, "DROP VIEW active_users");
38731    }
38732
38733    #[test]
38734    fn test_truncate() {
38735        let result = roundtrip("TRUNCATE TABLE users");
38736        assert_eq!(result, "TRUNCATE TABLE users");
38737    }
38738
38739    #[test]
38740    fn test_string_literal_escaping_default() {
38741        // Default: double single quotes
38742        let result = roundtrip("SELECT 'hello'");
38743        assert_eq!(result, "SELECT 'hello'");
38744
38745        // Single quotes are doubled
38746        let result = roundtrip("SELECT 'it''s a test'");
38747        assert_eq!(result, "SELECT 'it''s a test'");
38748    }
38749
38750    #[test]
38751    fn test_not_in_style_prefix_default_generic() {
38752        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38753        assert_eq!(
38754            result,
38755            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38756        );
38757    }
38758
38759    #[test]
38760    fn test_not_in_style_infix_generic_override() {
38761        let ast =
38762            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38763                .unwrap();
38764        let config = GeneratorConfig {
38765            not_in_style: NotInStyle::Infix,
38766            ..Default::default()
38767        };
38768        let mut gen = Generator::with_config(config);
38769        let result = gen.generate(&ast[0]).unwrap();
38770        assert_eq!(
38771            result,
38772            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38773        );
38774    }
38775
38776    #[test]
38777    fn test_string_literal_escaping_mysql() {
38778        use crate::dialects::DialectType;
38779
38780        let config = GeneratorConfig {
38781            dialect: Some(DialectType::MySQL),
38782            ..Default::default()
38783        };
38784
38785        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38786        let mut gen = Generator::with_config(config.clone());
38787        let result = gen.generate(&ast[0]).unwrap();
38788        assert_eq!(result, "SELECT 'hello'");
38789
38790        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38791        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38792        let mut gen = Generator::with_config(config.clone());
38793        let result = gen.generate(&ast[0]).unwrap();
38794        assert_eq!(result, "SELECT 'it''s'");
38795    }
38796
38797    #[test]
38798    fn test_string_literal_escaping_postgres() {
38799        use crate::dialects::DialectType;
38800
38801        let config = GeneratorConfig {
38802            dialect: Some(DialectType::PostgreSQL),
38803            ..Default::default()
38804        };
38805
38806        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38807        let mut gen = Generator::with_config(config.clone());
38808        let result = gen.generate(&ast[0]).unwrap();
38809        assert_eq!(result, "SELECT 'hello'");
38810
38811        // PostgreSQL uses doubled quotes for regular strings
38812        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38813        let mut gen = Generator::with_config(config.clone());
38814        let result = gen.generate(&ast[0]).unwrap();
38815        assert_eq!(result, "SELECT 'it''s'");
38816    }
38817
38818    #[test]
38819    fn test_string_literal_escaping_bigquery() {
38820        use crate::dialects::DialectType;
38821
38822        let config = GeneratorConfig {
38823            dialect: Some(DialectType::BigQuery),
38824            ..Default::default()
38825        };
38826
38827        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38828        let mut gen = Generator::with_config(config.clone());
38829        let result = gen.generate(&ast[0]).unwrap();
38830        assert_eq!(result, "SELECT 'hello'");
38831
38832        // BigQuery escapes single quotes with backslash
38833        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38834        let mut gen = Generator::with_config(config.clone());
38835        let result = gen.generate(&ast[0]).unwrap();
38836        assert_eq!(result, "SELECT 'it\\'s'");
38837    }
38838
38839    #[test]
38840    fn test_generate_deep_and_chain_without_stack_growth() {
38841        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38842            Expression::column("c0"),
38843            Expression::number(0),
38844        )));
38845
38846        for i in 1..2500 {
38847            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38848                Expression::column(format!("c{i}")),
38849                Expression::number(i as i64),
38850            )));
38851            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38852        }
38853
38854        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38855        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38856    }
38857
38858    #[test]
38859    fn test_generate_deep_or_chain_without_stack_growth() {
38860        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38861            Expression::column("c0"),
38862            Expression::number(0),
38863        )));
38864
38865        for i in 1..2500 {
38866            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38867                Expression::column(format!("c{i}")),
38868                Expression::number(i as i64),
38869            )));
38870            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38871        }
38872
38873        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38874        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38875    }
38876}